sábado, 3 de septiembre de 2005

Java 5. ¿Que tiene de bueno y de malo?. Parte II

Otros de los problemas que puede acarrear el mal uso de autoboxing son:

Problema con el == y el equals


Este no es un problema en si, de echo el uso del metodo equals o de == no produce ningun problema en la ejecución de código java. Sino que el problema esta en que se puede producir una confusión y un uso incorrecto.

La confusión viene por el echo de que en el rango [-128, 127], la VM mantienen los wrapper como constantes cuando realiza autoboxing, es decir que:

Byte b1 = 10;
Byte b2 = 10;

Se cumple la condición (b1 == b2), pero si hacemos:
Byte b1 = new Byte((byte)10);
Byte b2 = new Byte((byte)10);

No se cumple que (b1 == b2) dado que al realizar el autoboxing del tipo primitivo 10 al objeto Byte se ejecuta el método estático valueOf(...) que verifica si el primitivo esta dentro del rango mencionado, si lo esta devuelve una wrapper constante, sino crea un nuevo objeto con el valor.

Veamos un ejemplo:
public static void testEquals(Number n1, Number n2) {
    if( n1 == n2 ) {      // comparacion de referencias de objetos, no hay auto-boxing
        System.out.println("Las referencias son iguales");
    }

    if( n1.equals(n2) ) {
        System.out.println("Tienen el mismo valor");
    }
}
public static void main(String[] args) {
        Integer i = 120;
        Integer b = 120;
        testEquals(i, b);
}


El resultado es el siguiente:
- Las referencias son iguales
- Tienen el mismo valor
Ahora si hacemos:
public static void main(String[] args) {
        Integer i = 129;
        Integer b = 129;
        testEquals(i, b);
}

El resultado es distinto ya que las dos referencias no apuntan a los mismos objetos Integer aunque tengan el mismo valor.
Por lo tanto es siempre recomendable utilizar el método equals al comparar wrappers.

Problema con pasaje de parámetros a métodos sobrecargados.
Este problema lo mostrare con otro ejemplo. Supongamos tener la sigueinte clase:

public class Dummy {
    public void doIt(int x) {
        System.out.println("doIt(int x)");
    }

    public void doIt(Integer x) {
        System.out.println("doIt(Integer x)");
    }

    public void doIt(double x) {
        System.out.println("doIt(double x)");
    }

    public void doIt(Object x) {
        System.out.println("doIt(Object x)");
    }

    public static void main(String args...) {
        Dummy dummy = new Dummy();
        dummy.doIt(10);       
    }
}

Este código llama al metodo doIt(int x) y no hay ninguna duda de eso, pero que pasa ahora si este metodo no existe, a que metodo se llama?
Uno pensaria que se hace un autoboxing y se llama el método doIt(Integer x) dado que es el que corresponde por su tipo, pero no, se llama al metodo doIt(double x).
Porque pasa esto, bueno es por conpatibilidad con versiones anteriores a Tiger, por ejemplo, en java 1.4, al no existir autoboxing, las llamadas a los metodos se resolvian por compatibilidad de tipos. En este caso se llamaria a doIt(double x) y no a doIt(Integer x).
Ahora si tampoco existiera el metodo doIt(double x) si se realiza un autoboxing y se llama al metodo doIt(Integer x).

Por lo tanto podemos concluir con que la llamada a metodos se resuelve siguiendo el siguiente algoritmo:

1. El compilador intenta localizar el metodo sin utilizar boxing, unboxing o varargs.
2. Si el primer paso falla, el compilador intenta resolver el metodo permitiendo boxing y unboxing. varargs no es considerado.
3. Si el segundo paso fallo, el compilador intenta resolver el metodo una vez mas permitiendo boxing, unboxing y considerando los metodos con varargs.

Sin embargo el autoboxing es un feature que facilita mucho la programación en java y si es bien utilizado (y no se hace abuso de el) puede ser muy util a la hora de escribir codigo limpio y eficiente.

Una de las principales ventajas de autoboxing es en la utilización de collections, donde, junto al uso de generics, permite trabajar con tipos primitivos (agregar, sacar, etc) sin la necesidad de crear explicitamente objetos wrappers o realizar casteos de tipos, por ejemplo:
List<Long> longs = new LinkedList<Long>();.
longs.add(12L);            // auto-boxing y luego coloca el objeto en la lista
longs.add(Long.MAX_VALUE);

Las coleciones no soportan tipos primitivos, pero con el uso de autoboxing pareciera que si. En realidad lo que aqui se hace es:
- Hacer autoboxing de 12 a un Long con el value = 12
- Poner en la lista longs el objeto wrapper creado.

Para obtener los valores desde la collection:
long lValue = longs.get(0);        // Obtiene de la lista un objeto (Integer) y luego hace auto-unboxing
Long oValue = longs.get(1);

Lo que en realidad obtenemos de la lista (generics) es un objeto del tipo Long, y nuevamente con el uso de autoboxing, lo podemos trabajar como un tipo primitivo long sin realizar la llamada a los metodos del wrapper.

Como vemos aqui, no se escribio ningun casteo de tipos, esto es dado al uso de generic, que proximamente hablare de el.

No hay comentarios:

Publicar un comentario