17 de septiembre de 2011

Genericos y Colecciones Parte 1. Sobreescribiendo equals y hashCode

Genéricos y colecciones

Los genéricos fueron una de las características principales en el lanzamiento de Java 5. Con la salida de los genéricos cambio la forma en como podemos manejar colecciones en Java. Una de las características que tienen las colecciones es el poder ordenar y buscar entre los elementos que forman parte del contenido. Podemos establecer el criterio de búsqueda u ordenamiento para nuestras colecciones, pero para esto debemos sobrescribir los métodos equals y hashCode. En esta primera publicación tocaré el tema de sobrescribir los métodos equals y hashCode, las características de cada uno de estos métodos y algunos ejemplos.

Puntos a tocar en esta publicación:

  • Sobrescribiendo el método equals
  • Reglas que sigue el método equals
  • Ojo, mucho ojo.
  • Sobrescribiendo el método hashCode
  • ¿Que implica el hashcode?
  • Reglas que sigue el método hashCode
  • Ojo, mucho ojo.


Sobrescribiendo el método equals

Indagando en la javadoc acerca del método equals(Object o), podemos encontrar lo siguiente:

Método: equals(Object o)
Valor de retorno : boolean
Descripción: Indica si un objeto es igual a este.

Este método lo utilizamos para saber si un objeto es igual que otro. Este método utiliza el operador == para comparar a dos objetos y decidir si son iguales, por ejemplo podemos tener la siguiente clase:


public class Demo {

private int boleta;

public Demo(int boleta){
this.boleta = boleta;
}

public int getBoleta(){
return this.boleta;
}

public static void main(String[] args) {
Demo demoA = new Demo(20);
Demo demoB = new Demo(20);

System.out.println(demoA.equals(demoB));
}
}

La ejecución del anterior método main da como resultado en consola false. Esto es porque el método equals sin sobrescribir ocupa el operador == para comparar a dos objetos. Pero supongamos que queremos diferenciar a los objetos mediante su atributo boleta, ya que es el identificador de un Alumno para una aplicación escolar. Para esto debemos sobrescribir el método equals, diciéndole qué propiedad del objeto debe ser comparada para determinar si un objeto es igual a otro. El método sobrescrito se vería de esta forma:

@Override
public boolean equals(Object o){
if(( o instanceof Demo) && (((Demo)o).getBoleta() == this.boleta))
{
return true;
}else{
return false;
}
}

la tercera linea es la que tienen la magia. En ella hacemos un par de validaciones, la primera tiene que ver con estar seguros de que el objeto o es una instancia de la clase Demo, y la segunda es verificar si la propiedad boleta de el objeto o es igual a la propiedad boleta del objeto que invoco el método equals. Si los valores de boleta son los mismos, el método equals que sobrescribimos regresara true. Con esto nosotros tomamos la decisión sobre que criterio se debe tomar para comparar a nuestros objetos.

public static void main(String[] args) {
Demo demoA = new Demo(20);
Demo demoB = new Demo(20);

System.out.println(demoA.equals(demoB));
}

Por lo anterior, esta ejecución del método main, nos dará como resultado un true en la salida por consola.

Hay tipos de colecciones como los Sets que no nos permiten agregar objetos duplicados a la colección. La forma en como los Sets van a decidir si un objeto esta duplicado o no, la especificamos nosotros cuando sobrescribimos el método equals. Lo mismo cuando en una colección de tipo Hash* queremos buscar un elemento en especifico, la colección sabe que objeto regresar, dado el criterio que nosotros definimos al sobrescribir el método equals y hashCode. En esto radica la importancia de sobrescribir estos métodos, siempre y cuando este dentro de nuestras intenciones contar con este tipo de prestaciones.

Reglas que sigue el método equals

  • Reflexivo: Para cualquier referencia al valor x, x.equals(x) debe regresar true.
  • Simetrico: Para cualquier referencia a los valores x y z, x.equals(z) debe regresar true si y solo si z.equals(x) es true.
  • Transitivo. Para cualquier referencia a los valores w, x y z, si w.equals(x) regresa true y x.equals(z) regresa true, entonces w.equals(z) debe regresar true.
  • Consistente: Para cualquier referencia a los valores x y z, múltiples invocaciones a x.equals(z) consistentemente regresaran true o false, si es que los valores utilizados para la comparación de los objetos no ha sido modificada.
  • Para cualquier referencia no nula al valor x, x.equals(null), debe regresar false.

Ojo, mucho ojo

  • Asegúrate de sobrescribir el método equals. Las siguientes son implementaciones del método equals que son validas para el compilador, pero no validas para sobrescribir el método:
    • boolean equals(Objeto o). Esta implementación no sobreescribe el método equals de la clase Object, ya que el método debe ser declarado como public.
    • public boolean equals(Demo o). Esta implementación no sobreescribe el método equals de la clase Object, ya que el parámetro que necesita el método equals debe ser explícitamente un objeto de la clase Object, y no uno que extienda de éste. Esta implementación, al igual que la anterior es una sobrecarga del método equals, mas no sobreescribe este método.

La descripción correcta del método equals, es decir, la forma en como debe sobrescribirse es la siguiente:

public boolean equals(Objeto o);



Sobrescribiendo el método hashCode

¿Qué implica el hashcode?

Algunas colecciones usan el valor hashcode para ordenar y localizar a los objetos que están contenidos dentro de ellas. El hashcode es un numero entero, sin signo, que sirve en colecciones de tipo Hash* para un mejor funcionamiento en cuanto a performance. Este método debe ser sobrescrito en todas las clases que sobrescriban el método equals, si no se quiere tener un comportamiento extraño al utilizar las colecciones de tipo Hash* y otras clases. Si dos objetos son iguales según el método equals sobrescrito, estos deberian regresar el mismo hashcode. Véase el siguiente ejemplo:


public class Demo {

private int boleta;

public Demo(int boleta){
this.boleta = boleta;
}

public int getBoleta(){
return this.boleta;
}

@Override
public boolean equals(Object o){
if((o instanceof Demo) && (((Demo)o).getBoleta()== this.boleta))
{
return true;
}else{
return false;
}
}

public static void main(String[] args) {
Demo demoA = new Demo(20);
Demo demoB = new Demo(20);

System.out.println(demoA.equals(demoB));
System.out.println(demoA.hashCode());
System.out.println(demoB.hashCode());
}
}

Esto nos da como resultado:

true
1414159026
1569228633

regresa true porque estos objetos son iguales, debido a que se sobrescribió el método equals. Si son considerados iguales por equals, esto se reflejará al utilizarse en las colecciones de tipo Hash*. Lo que podemos ver en la salida de este código es que a pesar de que ambos objetos son iguales, el hashcode no es igual. Esto es porque no hemos sobrescrito el método hashCode.

El uso principal del hashcode, es como lo mencionamos arriba, cuando se manejan colecciones de tipo Hash*. La forma en como operan las colecciones de este tipo es a grandes rasgos la siguiente: Las colecciones de tipo Hash* almacenan los objetos en lugares llamados baldes, de acuerdo al numero obtenido por el método hashCode. Si el método hashCode regresa un 150, el objeto será guardado en el balde numero 150. Puede llegar a pasar que haya mas de un objeto de diferente tipo en el mismo balde. Esto no ocasiona ningún problema al momento de recuperar el objeto del balde, ya que al buscarlo este tipo de colecciones necesita como parámetro un objeto con el mismo valor hashcode, el cual utilizara para buscar el numero de balde que contiene a el objeto en cuestión. Si hay mas de un objeto, el siguiente criterio para determinar cual es el objeto buscado, es la utilización del método equals. Así es como este tipo de colecciones para buscar un objeto, ya sea para regresarlo y para ordenarlo. Lo cual falla si no sobrescribimos el método hashCode.

Sobrescribiendo el método hashCode

A continuación veremos como podremos sobrescribirlo:


@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + this.boleta;
return hash;
}

La salida que obtenemos una vez que se rescribe este método en el ejemplo de arriba es :

true
699
699

lo que nos dice que estos métodos son iguales según equals, y que además tienen el mismo numero hashcode.

Este método hashCode regresara consistentemente el mismo valor, siempre y cuando el campo boleta no cambie. Cada desarrollador puede implementar de diferente manera igualmente validas, correctas y eficientes este método. Lo que debemos de tener en mente al sobrescribir este método es que si para sobrescribir el método utilizamos variables de instancia (en nuestro ejemplo boleta), también debemos utilizar variables de instancia para generar un hashcode correcto. En el caso de las constantes que se utilizan en el ejemplo de arriba, se recomienda utilizar números primos, para una mejor distribución del hashcode generado.


Reglas que sigue el método hashCode

  • Si el método hashCode es invocado en múltiples ocasiones durante la ejecución de una aplicación, debe regresar consistentemente el mismo valor entero, esto si la información utilizada para calcular el hashcode no ha cambiado entre invocación e invocación del método hashCode.
  • Si dos objetos son iguales según el método equals, entonces la llamada al método hashCode debe regresar el mismo hashcode.
  • No es requerido que si dos métodos no son iguales según el método equals, tengan diferentes valores hashcode.

Ojo, mucho ojo

  • Si dos métodos son iguales según el método equals, el método hashCode debe regresar el mismo entero para ambos métodos. Sin embargo, si el métodos equals dice que dos métodos no son iguales, el método hashCode puede o no regresar el mismo entero.
  • Retornar un valor fijo en un método hashCode es una mala idea, ya que tendremos múltiples objetos con el mismo valor hashcode, lo cual no ayuda en nada a la hora de trabajar con colecciones de tipo Hash*.
  • Si utilizamos variables de clase de tipo transient para generar un hashcode, serializamos el objeto en cuestión y queremos recuperar el hashcode de ese método, nos encontraremos con que el hashcode será diferente al hashcode con que se serializó el objeto. Ya que este tipo de variables no se serializa. Por eso es una mala idea utilizar variables transient para generar el hashcode.

23 de abril de 2011

Ver mas alla

Las personas estudio o profesión aleja de la lectura de libros, periódicos y en general literatura de izquierda (y en general de cualquier lectura), estamos siendo tristemente burlados por un sistema al que no le interesa que nuestro segmento (y en general todos) se preocupe por la realidad del mundo. 

Los que estudiamos y/o laboramos cuestiones relacionadas con TI, somos unas de las victimas mas vulnerables, a pesar de haber tenido una educación a nivel superior. Nuestra ocupación es tan apasionante y absorbente que no nos preocupamos ni siquiera de estar informados de los temas que tienen un impacto palpable e incluso grave, en la vida de la sociedad en la que nos desenvolvemos. Ademas de que nuestra actividad encaja perfecto con el modelo neo-liberal, y que en este modelo no se nos trata tan mal.

Yo no quiero ser un zombie mas. Uno al que la televisión, el cine, la radio e incluso el Internet, le dicta qué hacer, como vestir, qué comer, etc. El grueso de la sociedad así es. Incluso, como ya mencione arriba, quienes se supone que tienen un criterio mas amplio, una visión menos angosta, por haber tenido la oportunidad de acceder a la educación superior, son a veces los que mas enrolados están en su papel de títere. 

Lo que nos salvara de esto y nos abrirá los ojos para cambiar la realidad por una en la que la gran mayoría de la población sea beneficiada, es la lectura, el dialogo, la movilización ciudadana, el no creer todo lo que leemos, vemos o escuchamos. El no confiarle nuestros oídos y ojos a cualquiera. Ser más personas y menos borregos.

3 de abril de 2011

Crear una libreria para una aplicacion Java ME

En mi ignorancia en aplicaciones mobiles ( y en java ), pensaba que el procedimiento para crear y agregar una libreria propia a un proyecto nuevo, era el mismo para la version de Java SE y ME. Pero estaba equivocado. 

Generalmente, cuando se quiere crear una libreria, solo se toma un proyecto, se construye (build), esto nos genera un archivo con extension ".jar" que es el que contiene las clases listas para ser agregardas a otro proyecto a manera de libreria. Entonces lo que hariamos en nuestro proyecto de Java SE seria agregar la libreria. Si estamos en Netbeans, bastaria con dar clic derecho en la carpeta de librerias de nuestro proyecto y seleccionar "add library". Y con esto podriamos hacer uso de nuestra libreria. 

Quise hacer este procedimiento para una aplicacion mobile que actualmente desarrollo (PipaApp). El compilador me decia que todo estaba correcto,  pero al momento de correr la aplicacion en el emulador obtenia este error:


java.lang.NoClassDefFoundError: com/blogspot/pplouis/saludo/Saludo
        at hello.PipaApp.getFormPrincipal(+2)
        at hello.PipaApp.startMIDlet(PipaApp.java:50)
        at hello.PipaApp.startApp(PipaApp.java:183)
        at javax.microedition.midlet.MIDletProxy.startApp(MIDletProxy.java:43)
        at com.sun.midp.midlet.Scheduler.schedule(Scheduler.java:374)
        at com.sun.midp.main.Main.runLocalClass(Main.java:466)
        at com.sun.midp.main.Main.main(Main.java:120)



Y es que no se puede agregar un archivo .jar creado con Java SE a un proyecto Java ME ( sin antes hacer unos cambios en un archivo de configuracion ), aunque se trate de solo una clase, que no haga ningun import y que solo imprima un hola mundo.  Un archivo .jar asi deberia de funcionar en ambar versiones de Java, pero no lo hace porque en el archivo META-INF, que se encuentra dentro del .jar, dice lo siguente:


Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.1
Created-By: 1.6.0_14-b08 (Sun Microsystems Inc.)
X-COMMENT: Main-Class will be added automatically by build


mientras que un archivo .jar creado para una app mobile de Java Me, tiene lo siguiente:


Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.1
Created-By: 1.6.0_14-b08 (Sun Microsystems Inc.)
MIDlet-Vendor: Vendor
MIDlet-Name: SaludoLibreriaMobile
MIDlet-Version: 1.0
MicroEdition-Configuration: CLDC-1.1
MicroEdition-Profile: MIDP-2.1


y se observa que hay especificaciones acerca de la configuracion y el perfil de la libreria.

 Para crear una libreria para Java Me es necesario que sea especificamente para un proyecto Java Me. Para esto haremos lo siguiente:

Primero creamos nuevo proyecto de tipo Java Me, dentro de las categorias que nos muestra ponemos que sea un "Mobile Class Library"


Despues se escoje la configuracion que y perfil que le queremos dar a nuestra libreria. Esto es importante, ya que esta libreria tendra mejor compatibilidad con las aplicaciones que tengan la misma configuración.


Ya creado el proyecto, solo necesitamos escribir nuestro codigo:




darle en netbeans "clean" o "clean and build".




 Esto nos genera el archivo distribuible, que para este caso sera un .jar, el cual es nuestra libreria, y que se encuentra en la carpeta dist de nuestro proyecto. 


La carpeta lib viene vacia, y el archivo Demos.jad, es basicamente un archivo de informacion, que generalmente se ocupa cuando se va a descar la aplicaciocion de algun sitio y sirve para dar informacion acerca de la version, tamaño del archivo, configuracion , etc.

Y ya por ultimo solo basta agregarla a nuestro proyecto Java Me. Para eso damos clic derecho en la carpeta de "Resources", despues en "add jar/zip" y agregarmos el archivo .jar generado previamente.  


24 de marzo de 2011

error: The virtual machine does not support this operation: schema change not implemented

Los errores de Netbeans. cuando estas en modo debugger para una app: puedes hacer cambios en el codigo y verlos reflejados en la aplicacion sin que tengas de desplegarla otra vez. Esto agiliza el proceso de debuggear.

Hay ciertos cambios que no los puede actualizar, como por ejemplo el añadir o elminar un field o variable de clase, cuyo error cuando queramos "Apply Code Changes" es:

The virtual machine does not support this operation: schema change not implemented
D:\SACMx\SACMx6.0.0\nbproject\build-impl.xml:914: The following error occurred while executing this line:
D:\SACMx\SACMx6.0.0\nbproject\build-impl.xml:401: The virtual machine does not support this operation: schema change not implemented
BUILD FAILED (total time: 0 seconds)

Esto es porque la implementacion de la maquina virtual no lo permite. Si navegamos hasta build-impl.xml en la linea 914 y build-impl.xml en la linea 401, nos daremos cuenta de que es un error al querer recargar una clase

Ademas de esta condicion no se podra "Apply Code Changes" cuando el codigo tenga errores de compilacion (obviamente).