Rumbo a nuestra Certificación (Tercera entrega)
Hola a todos los seguidores de Java, cómo les va?
Como ya se ha hecho costumbre seguimos con nuestros pequeños aportes para prepararnos en obtener la Certificaciòn de Expert Java Programmer.
Esta entrega contempla los siguientes temas:
a) Igualdad de objetos.
b) Operadores Lógicos.
c) Operador instanceof.
d) Operador condicional.
e) El recolector de basura (Garbage Collector).
Cualquier duda, comentario o aclaración por favor en el panel correspondiente.
Saludos y no caigamos en excepción alguna, je je je!
Igualdad de objetos.
Los operadores de igualdad (==) y desigualdad (!=) pueden utilizarse para comparar cualquier tipo compatible.
Ahora bien, si los utilizamos para comparar variables de tipo objeto debemos tener en cuenta que lo que contienen dichas variables son referencias a objetos, no los objetos en sí.
Esto implica que podemos tener dos variables referenciando a dos objetos iguales y que la condición de igualdad de las variables resulte falsa.
A continuación anexamos un ejemplo:
En el ejemplo anterior se aprecia que existen dos objetos de la clase String con el mismo valor de texto ("hola"), situados en zonas de memoria diferentes (referencias diferentes), por lo que al compararlos el programa indicará que no son iguales.
Para comparar la igualdad de objetos, las clases proporcionan un método llamado equals, en el que cada clase implementa su criterio de igualdad. Por lo tanto si queremos saber si dos objetos de una determinada clase son iguales se debe utilizar el método equals no el operador de comparación (==), el cual sólamente daría un resultado verdadero si las dos variables apuntan al mismo objeto.
Por último debemos tener en cuenta que el operador de igualdad (==) sólo puede comparar referencias del mismo tipo de objeto; si se comparan dos variables de tipos de objeto diferentes, se producirá un error de compilación. A continuación anexamos un ejemplo:
Operadores Lógicos.
Operan con los valores de tipo boolean, siendo el resultado también de tipo boolean.
A continuación se describe cada uno de ellos:
a) Operador lógico AND (&&): El resultado será true si los dos operandos son true, en cualquier otro caso el resultado será false.
b) Operador lógico OR (||): El resultado será true si alguno de los operandos es true.
c) Operador lógico NOT (!): Actúa sobre un único operando de tipo boolean, dando como resultado el valor contrario al que tenga el operando.
Cabe mencionar que los operadores AND (&&) y OR (||) funcionan en modo "cortocircuito", esto significa que si el primer operando determina el resultado de la operación, el segundo operando no será evaluado.
A continuación anexamos un ejemplo:
Al ejecutarse dicho código se imprimirá en pantalla lo siguiente:
Lo que demuestra que la expresión incluida en el segundo operando de OR (||) no llega a ejecutarse pues, al ser true el primer operando (p > 0), el resultado de toda la expresión será directamente true.
Operador instanceof.
Opera únicamente con referencias a objetos y se utiliza para saber si un objeto pertenece a determinado tipo. La forma de utilizarlo es la siguiente:
referencia_objeto instanceof clase
El resultado de la operación será true si el objeto pertenece a la clase especificada o a una de sus subclases.
A continuación anexamos un ejemplo:
Al ejecutarse dicho programa mostrará en pantalla: El objeto es una cadena.
Operador condicional.
Se trata de un operador ternario (consta de tres operadores) cuya función es asignar un valor entre dos posibles a una variable, en función del cumplimiento o no de una condición. Su formato es el siguiente:
tipo variable = (condicion)?valor_si_true:valor_si_false
Si la condición resulta verdadera (el resultado es true), se almacenará en la variable el resultado de la expresión valor_si_true, si no, se almacenará valor_si_false.
A continuación anexamos un ejemplo:
El recolector de basura de Java.
El recolector de basura (Garbage Collector) es una aplicación que forma parte de la JVM (Java Virtual Machine) y cuyo objetivo es liberar de la memoria los objetos no referenciados. Cuando un objeto deja de estar referenciado, se le marca como "basura", a partir de entonces la memoria que ocupa puede ser liberada por el recolector.
La desición de en que momento se va a ejecutar el recolector de basura depende de la implementación de la máquina virtual, por lo tanto, en Java no es posible saber en qué momento exacto un objeto será destruido, sólo es posible determinar el momento en que esto puede suceder (cuando se elimine la última referencia al mismo).
Si un objeto va a dejar de ser utilizado en un programa, conviene eliminar las referencias al mismo para que sea marcado como "basura". Esto se puede hacer asignando el valor "null" a la variable o variables que apunten al objeto.
Aunque no asignemos el valor de "null" a la variable que apunta al objeto, en el momento en que ésta salga de ámbito, se perderá la referencia y el objeto será etiquetado como "basura".
---------------------------------------------------
Miércoles 13 de mayo del 2009.
Jhanno Foulds Gaunt - Km3.
- Jhanno's blog
- Inicie sesión o regístrese para enviar comentarios
gracias por el aporte
Gracias por el aporte hay cosas simples que a veces se olvidan o se pasa por alto pero siempre conviene recordarlas
Incorrección
Por favor eliminen este párrafo:
Esto no es conveniente, es redundante, ¿dónde lo leyeron?
Javier Castañón
(J2ME) Conveniente, no redudante, práctico
Beginning J2ME, From Novice to Professional.
En contextos limitados como J2ME, es práctico, conveniente y no es redudante marcar referencias nulas.
Una forma de ayudarle al garbage collector a encontrar la "basura" en dispositivos con capacidades limitadas es marcando referencias nulas para evitarle el trabajo de buscar referencias perdidas.
Para J2SE no tiene sentido práctico pero es importante saberlo ya que el objetivo de la certificación es conocer un poco más del lenguaje (Como tip para SCJP, estudien la forma en que se entrelazan las referencias y el garbage collector las barre)
Muchas gracias por el
Muchas gracias por el aporte. Debo indicar y reiterar que todo el texto y ejemplos que publicamos están probados y revisados con 3 diferentes materiales para la Certificación. Nos esforzamos por dar a los novatos el mejor material Web para su certificación. Cualquier duda estamos a sus órdenes. Saludos cordiales.
Permítaseme disentir y explicar
Nadie discute el objetivo de aprendizaje, que para el contexto de la certificación es muy claro: cuando un objeto ya no tiene referencias hacia él, es candidato para ser recogido por el colector de basura. Eso es muy distinto a sugerir que es buena práctica nulificar las referencias a los objetos cuando dejan de ser utilizados, salvo contadas excepciones.
Abriré un post de blog para explicar por qué en general es redundante nulificar las referencias por defecto, basado en los escritos de dos de los múltiples autores del JDK, a saber: James Gosling y Joshua Bloch publicados nada más ni nada menos que por Sun. A partir de ahí se comprenderá:
1) Por qué las convenciones de codificación de Sun no recomiendan nulificar las referencias
2) Por qué el código fuente del JDK no nulifica las referencias. (No me crean, ¡compruébenlo!)
3) Por qué en general es mala idea invocar System.gc()
4) Por qué el colector de basura puede recoger objetos que aún tengan referencias hacia ellos.
5) Por qué el colector de basura puede recoger objetos aún antes de ser anuladas sus referencias.
6) Cuándo sí es deseable nulificar referencias para prevenir goteos de memoria.
Como un adelanto, voy a transcribir un fragmento de un librito llamado "Tha Java Programming Language, 4th Edition" escrito por Ken Arnold, James Gosling y David Holmes
Ahora transcribiré otro pedacito de un librito llamado "Effective Java Programming, 2nd Edition" escrito por un señor llamado Joshua Bloch (las negritas no son mías, son del señor Bloch):
Debo aclarar que todo esto es material avanzado, que la certificación no requiere que sepan toda esta arcana, pero me parece importante señalarles que aún los libros contienen errores, por eso pregunté "¿Dónde leyeron esto?" Por cierto, consulté también el libro de certificación de Kathy Sierra, y no hace la recomendación de nulificar.
Debo decir también que antes de hacer esta entrada de blog, requiero hacer otra explicando cómo utilizar JPA y Hibernate. Espero comprenderán que el tiempo es limitado :-O
Saludos cordiales
Javier
Edición: Joshua Bloch establece también la necesidad de nulificar las referencias a objetos que representan estructuras de datos, como el autor del libro J2ME. En este último caso, no está recomendando nulificar todas las referencias a objetos. Por último, preguntaría: si "es práctico, conveniente y no es redudante marcar referencias nulas" ¿por qué no es una práctica que sigan los programadores del JDK, Eclipse, etc.? (ojo, en el caso de Eclipse no cuentan dereferenciar el código SWT, pues no es Java puro, sino un híbrido con C).
Permíteme insistir
¿En cuál material dice que se "conviene eliminar las referencias al mismo para que sea marcado como "basura"? Tal vez se trate sólo de un error de traducción. Notas:
1) Nadie está discutiendo que nulificar la única referencia a un objeto sea una estrategia válida para marcarlo como candidato a ser recogido por el colector de basura.
2) La referencia al libro de J2ME establece un contexto muy claro para nulificar: asignar cualquier estructura de datos a null previene goteos de memoria. No sugiere que se haga para todas las variables.
Saludos
Javier
Clarificando los objetivos de la Certificación
Agradecemos todos los aportes que se han suscitado de un tema tan especial como el "Garbage Collector", a continuación y para efecto de la comprensión para los novatos, citaré un fragmento del Libro "Piensa en Java".
La recolección de basura no es destrucción.
Si se recuerda esto, se evitarán los problemas. Lo que significa que si hay alguna actividad que debe llevarse a cabo antes de que un objeto deje de ser necesario, hay que llevar a cabo esa actividad por uno mismo. (Bruce Eckel).
Recomiendo el capítulo denominado: "Inicialización y limpieza" de "Piensa en Java" para aunar en el tema.
Saludos cordiales.
2) La referencia al libro
Nadie sugirió que se hiciera para todas las variables
Sería un buen aporte, así las personas que estudian para la certificación conocerán más del lenguaje y buenas prácticas de programación en JSE, JEE.
Por que te falto incluir toda la frase:
Buen punto
Antes de que el colector de basura reclame la memoria de un objeto, éste debe estar eun estado "finalizable", y antes de eso, el objeto debe ser "alcanzable". Imaginemos un televisor y tres controles remotos (mandos a distancia). El televisor está prendido y es el objeto y los controles remotos las referencias (o variables). Hay una persona que debe llevarse el televisor, pero no lo puede hacer mientras haya un control remoto activo y el televisor esté encendido.
A cada control remoto esperamos que se le acaben las pilas (baterías) o se las quitamos (quedan fuera de alcance o las nulificamos, respectivamente). Cuando la persona que debe llevarse el televisor verifica que ya nadie puede enviarle señales al televisor, entonces significa que el aparato es inalcanzable para los demás pero alcanzable para él. En ese momento lo apaga (lo finaliza, es decir invoca el método
) y a continuación se lo lleva (reclama la memoria).
Los objetos escritos en C++, tienen unos métodos especiales llamados destructores, que deben ser invocados manualmente y que sirven para liberar recursos y liberar el espacio en memoria. En Java no hay destructores, y si hay que liberar un recurso como una conexión a una base de datos o un "handle" de un archivo, debe hacerse manualmente tan pronto como se pueda, y no poner este código en el método
pues no se sabe cuánto tiempo pasará para que el colector de basura haga su trabajo, aún si lo invocamos con
, pues simplemente se puede negar a obedecernos. Su comportamiento no es determinístico, es más bien probabilístico. Luego entonces, la recolección de basura en Java no es como la destrucción de objetos en C++.
Saludos
Javier
No importa que sea J2ME
Veamos el problema con la redacción, dado que está diriga a novatos:
Entonces, en mi código hago lo que me me dicen que es conveniente:
Esto es innecesario y redundante en J2ME o en J2SE. ¿Me explico? El autor del libro que citas es claro respecto a nulificar referencias a estructuras de datos, ello para evitar goteos de memoria. ¿Cuál puede ser una estrategia agresiva en J2ME? Una que aplique tal vez a variables miembro. Cito:
Fuente:
Aún en dicho caso, ambas estrategias son aplicables a J2ME y J2SE
Entonces mi propuesta de redacción es la siguiente:
Saludos
Javier Castañón
Edición: Nótese que también estoy sugiriendo juntar dos párrafos en uno solo, para hacerlo aún más explícito.
Bien
Hola Javier:
Coincido con tu propuesta de redacción para no confundir a la gente que estudia para la certificación
Mi propuesta de redacción es la misma, solo agregaría 3 palabras para que no se mal entienda:
En contextos limitados como J2ME, es práctico, conveniente y no es redudante marcar referencias nulas bajo ciertas circunstancias
De hecho en j2me se rompe con muchas best practice, por citar un ejemplo se recomienda NO utilizar la encapsulación bajo ciertas circunstancias, pero ya nos estamos metiendo en otros temas..
Un saludo
El caso redundante ya quedó ejemplificado
Y me parece que es explícito en cuanto a lo que considero un caso redundante e inconveniente. Pero creo que debemos ser explícitos respecto a cuáles son "ciertas circunstancias", para que un novato las pueda distinguir.
Yo tomaría dos heurísticas más allá de lo que es indispensable saber para certificarse (hay gente certificada que no comprende adecuadamente las causas de los goteos de memoria, por ejemplo):
- Las estructuras de datos y sus miembros hay que vigilar que sean adecuadamente nulificadas según corresponda.
- Hay que cuidarse de aquellas referencias que son externas a nuestra instancia.
Voy a ejemplificar utilizando casos de "goteos de memoria" Un goteo de memoria se da cuando una aplicación no libera la memoria consumida y que ya no usa, y conforme pasa el tiempo consume más y más memoria.
Un ejemplo del libro de Joshua Bloch, crea una pila con goteo de memoria. Se asume que el lector conoce cómo funciona una pila.
El pecado está en el método
Al no nulificar la referencia a un elemento de la matriz cuando éste ya no será utilizado (al menos no por nuestra instancia de Stack), la referencia aunque salga de ámbito, no será reclamada por el colector de basura, pues nosotros guardamos inadvertidamente una referencia a ella en nuestra matriz. Entonces o nulificamos el elemento o nulificamos la matriz completa para evitar el goteo de memoria.
Ya que mencionaste el encapsulamiento, se me ocurrió un ejemplo donde el adecuado encapsulamiento reduce el riesgo de gotear memoria. Al igual que el ejemplo anterior, aplica en cualquier edición de Java, pero en este caso, el problema no se resolverá nulificando referencias.
Considerando una instancia de una clase Bar, que pasa una referencia de Date a una instancia de Foo, ésta última no podrá morir hasta que se muera la insancia de Bar. ¿La razón? No se está encapsulando/ocultando la información adecuadamente: Bar tiene una referencia a una instancia de Date que pasó a una instancia de Foo. Aunque Foo se quera morir, tiene que esperar que Bar muera o elimine su referencia a la instancia de Date, y viceversa:
A continuación un código que con encapsulamiento evita posibles goteos de memoria:
En este caso se codificó el método
de una manera defensiva: se obtiene una copia local de la instancia de Date. Discutiblemente, ocupa más memoria porque crea un objeto adicional a si sólo se pasara la referencia, pero dado que la instancia de Foo ya no está conectada con la instancia de Bar, el colector de basura puede reclamar a cualquiera de los dos por separado: podría ocurrir que gracias a que se permite al colector de basura haga su trabajo, termine ocupando menos memoria al paso del tiempo.
Saludos
Javier Castañón
Ejemplo práctico
El ejemplo que pones de goteos de memoria no creo que le sirva de forma práctica a un novato, pero me parece acertado de tu parte dar un ejemplo explicito para que los programadores que lean esto sepan que no todo lo que brilla es oro y una mala programación puede llevar a fugas de memoria.
Aclaro, romper con la encapsulación en dispositivos con capacidades limitadas sirve para mejorar el performance y optimizar el tamaño de la aplicación cuando no es suficiente con una obfuscación.
En el micro mundo, los programadores que piensan que tiene terminada una aplicación cuando les corre en un emulador, viven en el jardín del edén ya que no llevan ni el 60% de la tunda que les espera en el mundo real. En dispositivos con capacidades limitadas se debe romper y olvidar muchas las best practices de la programación standar o empresarial, en ocaciones debes bajar a un nivel de abstración tal que los operadores a nivel de bit son el pan de cada día para ciertas operaciones aritméticas y empaquetado de información.
Y ya que mencionas el stack, en J2ME se recomienda pasar en medida de lo posible pocos parámetros en métodos que son llamados frecuentemente, si se requiere pasar una buena cantidad de variables es mejor declararlas a nivel de clase, es decir en el heap (es muy reducido, pero que muy reducido su tamaño)
Para enriquecer la plática, voy a poner un caso práctico de dónde marcar referencias nulas en dispositivos con capacidades limitadas ya para terminar con esto por que se está haciendo muy largo, pero antes del caso práctico es importante resaltar los siguientes puntos:
El siguiente es un trozo de código tomando de Beginning J2ME from Novice to Professional para realizar una conexión por bluetooth/obex, tomen en cuenta que se está en un contexto multithread:
Lo importante está aquí:
El ciclo terminará de la mejor forma cuando mEndNow=true, tan pronto marquemos mConnection = null será elegible por el garbage collector incluso aunque no haya salido del ciclo while.
Espero depués de todo esto los programadores que lean este post tengan una idea más clara de dónde marcar referencias nulas y bajo que contextos aplicarlo.
Un saludo
Ejemplo práctico
iberck:
Javier:
Creí que había dejado en claro el contexto, su relación con la certificación y sus consecuencias prácticas.
Pasando a otra cosa, sería bueno agregar un
para que no se ocasione un
¿no?
Por último, reitero que mis observaciones han estado orientadas hacia la precisión técnica y prácticas comúnmente seguidas en la plataforma Java Standard Edition (no acostumbro ni me atrevo a llamarlas mejores prácticas), que es a la que va dirigida la certificación SCJP. De ninguna manera la intención ha sido irritar a nadie, y sinceramente espero que en el futuro podamos tener nuevas y más discusiones técnicas en español, en lo particular he disfrutado mucho este intercambio que considero enriquecedor.
Afectuosamente
Javier Castañón
... Y todo se resume al contexto...
Creo que ya se había comentado anteriormente (y con mucho énfasis) que el hacer asignación al valor null es necesario/permitido en contextos muy específicos de acuerdo al problema que se esté intentando solucionar. Ambos, los últimos ejemplos, citan claras aplicaciones y excepciones a dicha regla. Mi punto es el de notificar que el hilo del tema principal se va desviando cada vez un poco más, aunque con interesantes citas y aclaraciones, podría confundir realmente a principiantes, y no es algo en lo que tengan que preocuparse inicialmente del todo. Ah, y qué yo intentaré desviarlo incluso más con lo siguiente:
El hecho de citar textos no implica que esté libre de errores o que el autor no esté violando alguna regla de "mejor práctica" en el intento, a fin de cuentas, el tópico abordado por el autor está obviamente sujeto a su experiencia y en muchas ocasiones, los autores al redactar ejemplos se brincan muchas de estas situaciones por "claridad" o por ahorrar incluso espacio. Obviamente lo anterior es dependiendo de quién redacta el libro y que tan "experto" es del tema (por aquello de citar a Joshua Bloch y otras deidades).
Habiendo dicho esto, incluso en el ejemplo del MIDlet hay cuestiones que me hacen pensar que no por ser un programa que va a ser utilizado por un dispositivo con limitados recursos y con el propósito de "enseñar", puedes violar reglas muy sencillas al desarrollar una aplicación concurrente. Por ejemplo, falta de uso de try-finally para garantizar que realmente los recursos como conexiones están cerrados. El uso de volatile a variables de estado (mEndNow) que garantize visibilidad de memoria adecuada ante la presencia de múltiples contextos de ejecución (aquí citaría a Brian Goetz). Y por último... a mí no se me parece que el autor hace la asignación de null a mConnection por la cuestión de permitirle al garbage collector disponer de ella antes; tan sencillo como revisar al salir del ciclo que el recurso esté cerrado, y si no lo está, simplemente hacerlo. Como con las conexiones en JDBC, el que un objeto (Connection, Statement, ....) pueda ser elegible para recolección de basura no implica que el recurso debajo de él pueda ser cerrado/limpiado correctamente, es decir.. si olvidas invocar close() el gc no garantiza (ni el driver) que cualquier recurso que tu objeto estaba utilizado va a ser cerrado.
Otro punto digno de citar, ya que mencionamos a Joshua Bloch es que... incluso con las APIs hay errores/deficiencias, y Java tiene los suyos como lenguaje de programación e incluso con la API estándar(qué sería otro tópico distinto), a lo cual Joshua Bloch cita frecuentemente y en negritas en Effective Java Programming. También los diseñadores de APIs tienen resbalones.
Amén.
Participen en los foros
Javier:
Por simplicidad tomé el código tal cual del libro, dejemos que alguien más participe en este post y responda dónde y por que agregar la sentencia if para no causar un NPE
Javier:
Tienes razón, simplemente no se me hizo un ejemplo práctico y por lo mismo expuse uno.
Javier:
Ni la mia, me da gusto compartir con gente como tú. La forma en la que has expuesto el tema ha sido técnicamente impecable y cada quien desde un enfoque distinto dio su punto de vista. De igual manera invito a que todos participen activamente en los foros y blogs ya que es otra manera de aprender.
Un saludo
Último post
En la primer parte preguntaste y respondiste tú mismo.
amnesiac:
amnesiac:
amnesiac:
mEndNow no necesita ser volatile ni estar sincronizada por que su valor no es modificado en el ciclo de vida de los threads, el único que la toca es el método destroyApp() al finalizar el MID.
amnesiac:
Y si se termina la memoria/recursos antes de salir del ciclo ? Recuerda estamos en un contexto limitado donde cada recurso es oro molido.
Totalmente de acuerdo, ningún programador es perfecto y ningún sistema libre de bugs.
Un saludo