Autoboxing y Unboxing en Java

Avanzado

A partir de JDK 5, Java ha incluido dos características muy útiles: autoboxing y auto-unboxing. El autoboxing/unboxing simplifica y agiliza en gran medida el código que debe convertir los tipos primitivos en objetos, y viceversa. Debido a que tales situaciones se encuentran con frecuencia en el código de Java, los beneficios del autoboxing/unboxing afectan a casi todos los programadores de Java.

Autoboxing/unboxing está directamente relacionado con los wrappers de Java y con la forma en que los valores se mueven dentro y fuera de una instancia de un wrapper (envoltorio). Por esta razón, comenzaremos con una descripción general de los tipos de wrappers y el proceso boxing
y unboxing manual de los valores.

1. Wrappers de tipo

Como sabe, Java usa tipos primitivos, como int o double, para contener los tipos de datos básicos admitidos por el lenguaje. Los tipos primitivos, en lugar de objetos, se utilizan para estas cantidades por el bien del rendimiento. Usar objetos para estos tipos básicos agregaría una sobrecarga inaceptable incluso para los cálculos más simples. Por lo tanto, los tipos primitivos no son parte de la jerarquía de objetos, y no heredan de Object.

A pesar del beneficio de rendimiento que ofrecen los tipos primitivos, hay momentos en los que necesitará una representación de objetos. Por ejemplo, no puede pasar un tipo primitivo por referencia a un método. Además, muchas de las estructuras de datos estándar implementadas por Java operan en objetos, lo que significa que no puede usar estas estructuras de datos para almacenar tipos primitivos. Para manejar estas situaciones (y otras), Java proporciona wrappers de tipo, que son clases que encapsulan un tipo primitivo dentro de un objeto. A continuación, analizaremos los wrappers más de cerca.

Los envoltorios (wrapper) de tipo son Double, Float, Long, Integer, Short, Byte, Character, y Boolean, que están empaquetados en java.lang. Estas clases ofrecen una amplia gama de métodos que le permiten integrar completamente los tipos primitivos en la jerarquía de objetos de Java.

1.1. Wrappers de tipo numérico

Probablemente las envolturas de tipo más utilizadas son aquellas que representan valores numéricos. Estos son Byte, Short, Integer, Long, Float y Double. Todos los envoltorios de tipo numérico heredan de la clase abstracta Number. Number declara métodos que devuelven el valor de un objeto en cada uno de los diferentes tipos numéricos. Estos métodos se muestran aquí:

byte byteValue()
double doubleValue()
float floatValue()
int intValue()
long longValue()
short shortValue()

Por ejemplo, doubleValue() devuelve el valor de un objeto como un double, floatValue() devuelve el valor como un float, y así sucesivamente. Estos métodos son implementados por cada uno de los envoltorios de tipo numérico.

Todas las envolturas de tipo numérico definen constructores que permiten que un objeto se construya a partir de un valor dado, o una representación de cadena de ese valor. Por ejemplo, aquí están los constructores definidos para Integer y Double:

Integer(int num)
Integer(String str) throws NumberFormatException
Double(double num)
Double(String str) throws NumberFormatException

1.2. Uso de valueOf() en Wrapper

Si str no contiene un valor numérico válido, entonces se lanza un NumberFormatException. Sin embargo, a partir de JDK 9, los constructores wrapper de tipo han quedado obsoletos. Actualmente, se recomienda que use uno de los métodos valueOf() para obtener un objeto wrapper. El método valueOf() es un miembro estático de todas las clases wrappers y todas las clases numéricas admiten formas que convierten un valor numérico o una cadena en un objeto. Por ejemplo, aquí hay dos formas compatibles con Integer:

static Integer valueOf(int val)
static Integer valueOf(String valStr) throws NumberFormatException

Aquí, val especifica un valor entero y valStr especifica una cadena que representa un valor numérico correctamente formateado en forma de string. Cada uno devuelve un objeto entero que envuelve el valor especificado. Aquí hay un ejemplo:

Integer iOb = Integer.valueOf(100);

Después de que se ejecute esta instrucción, el valor 100 se representa mediante una instancia entera. Por lo tanto, iOb envuelve el valor 100 dentro de un objeto.

Todas las envolturas de tipo anulan a String(). Devuelve la forma legible por el ser humano del valor contenido en la envoltura. Esto le permite dar salida al valor pasando un objeto wrapper de tipo a println(), por ejemplo, sin tener que convertirlo en su tipo primitivo.

1.3. boxing y unboxing

El proceso de encapsular un valor dentro de un objeto se llama boxing. Antes de JDK 5, todo el boxing se llevaba a cabo manualmente, con el programador construyendo explícitamente una instancia de una envoltura con el valor deseado, como se acaba de mostrar. Por lo tanto, en el ejemplo anterior, se dice que el valor 100 está envuelve (boxed) dentro de iOb.

El proceso de extraer un valor de un contenedor de tipo se denomina unboxing. De nuevo, antes de JDK 5, todo el desempaquetado también se realizó manualmente, con el programador llamando explícitamente a un método en el contenedor para obtener su valor. Por ejemplo, esto unboxes manualmente el valor en iOb en un int.

El proceso de extraer un valor de una envoltura de tipo se denomina unboxing. De nuevo, antes de JDK 5, todo el unboxing también se realizaba manualmente, con el programador llamando explícitamente a un método en la envoltura para obtener su valor. Por ejemplo, esto desenvuelve (unboxes) manualmente el valor de iOb en un int. (si tienes una mejor traducción para explicar el término boxing, por favor comentarla)

int i = iOb.intValue()

Aquí, intValue() devuelve el valor encapsulado dentro de iOb como un int. El siguiente programa demuestra los conceptos anteriores:

//Demostración manual de boxing y unboxing con wrapper
class Wrapper {
    public static void main(String[] args) {
        Integer iOb = Integer.valueOf(100);
        int i=iOb.intValue();
        System.out.println(i+ " "+iOb);
    }
}

Salida:

100 100

Este programa envuelve el valor entero de 100 dentro de un objeto Integer llamado iOb. El programa obtiene este valor llamando a intValue() y almacena el resultado en i. Finalmente, muestra los valores de i e iOb, ambos de los cuales son 100.

El mismo procedimiento general utilizado en el ejemplo anterior de box y unbox de los valores era necesario para todas las versiones de Java anteriores a JDK 5 y todavía puede encontrarse en el código heredado. El problema es que es tedioso y propenso a errores porque requiere que el programador cree manualmente el objeto apropiado para envolver un valor y obtener explícitamente el tipo primitivo apropiado cuando se necesita su valor. Afortunadamente, el autoboxing/unboxing mejora fundamentalmente estos procedimientos esenciales.

2. Fundamentos de Autoboxing

Autoboxing es el proceso por el cual un tipo primitivo es automáticamente encapsulado (boxed) en su envoltura (wrapper) de tipo equivalente cuando se necesita un objeto de ese tipo. No hay necesidad de obtener explícitamente un objeto. El Auto-unboxing es el proceso por el cual el valor de un objeto encapsulado se extrae automáticamente (desencapsula) de una envoltura de tipo cuando se necesita su valor. No hay necesidad de llamar a un método como intValue() o doubleValue().

La adición de autoboxing y auto-unboxing agiliza en gran medida la codificación de varios algoritmos, eliminando el tedio de los valores de boxeo manual y de desembalaje. También ayuda a prevenir errores. Con el autoboxing no es necesario construir manualmente un objeto para envolver un tipo primitivo. Solo necesita asignar ese valor a una referencia de tipo contenedor. Java construye automáticamente el objeto por ti. Por ejemplo, esta es la forma moderna de declarar un objeto entero que tiene el valor 100:

La adición de autoboxing y auto-unboxing agiliza enormemente la codificación de varios algoritmos, eliminando el tedio del boxing y unboxing. También ayuda a prevenir errores. Con el autoboxing no es necesario construir manualmente un objeto para envolver un tipo primitivo. Sólo necesita asignar ese valor a una referencia de envoltura de tipos. Java automáticamente construye el objeto para usted. Por ejemplo, aquí está la forma moderna de declarar un objeto entero que tiene el valor 100:

Integer iOb = 100;

Observe que el objeto no está encapsulado explícitamente. Java maneja esto para usted, automáticamente.
Para desencapsular un objeto, simplemente asigne esa referencia de objeto a una variable de tipo primitivo.

Por ejemplo, para desencapsular iOb, puede usar esta línea:

int i = iOb; // auto-unbox

Java maneja los detalles por ti. El siguiente programa demuestra las declaraciones anteriores:

//Demostración de autoboxing y unboxing
class Autobox {
    public static void main(String[] args) {
        Integer iOb = 100;
        int i=iOb;
        System.out.println(i+ " "+iOb);
    }
}

Salida:

100 100

3. Autoboxing y métodos

Además del caso simple de asignaciones, el autoboxing se produce automáticamente siempre que un tipo primitivo se debe convertir en un objeto, y el auto-unboxing tiene lugar cada vez que un objeto se debe convertir a un tipo primitivo. Por lo tanto, el autoboxing/unboxing puede ocurrir cuando un argumento se pasa a un método o cuando un método devuelve un valor. Por ejemplo, considere lo siguiente:

// El autoboxing / unboxing se lleva a cabo
// con parámetros de método y valores devueltos.
class Autobox {
   //Este método tiene un parámetro Integer.
    static void m(Integer v){
        System.out.println("m() recive "+v);
    }

    //Este método devuelve un int
    static int m2(){
        return 10;
    }

    //Este método devuelve un Integer
    static Integer m3(){
        return 99;
    }

    public static void main(String[] args) {
        //Pasar una int a m().
        //Debido a que m() tiene un parámetro Integer,
        //el valor int pasado es automáticamente encapsulado.
        m(199);

        //Aquí, iOb recibe el valor int devuelto por m2().
        //Este valor se encapsula automáticamente
        //para que pueda asignarse a iOb
        Integer iOb=m2();
        System.out.println("El valor de retorno de m2() es: "+iOb);

        //A continuación, se llama m3().
        //Esto devuelve un valor Integer
        //que se desencapsula automáticamente en un int.
        int i=m3();
        System.out.println("El valor de retorno de m3() es: "+i);

        //A continuación, se llama a Math.sqrt() con iOb como argumento.
        //En este caso, iOb se desencapsula automáticamente
        //y su valor se promociona a double, que es el tipo que necesita sqrt().

        iOb=100;
        System.out.println("La raíz cuadrada de iOb es: "+Math.sqrt(iOb));
    }
}

Salida:

m() recive 199
El valor de retorno de m2() es: 10
El valor de retorno de m3() es: 99
La raíz cuadrada de iOb es: 10.0

En el programa, observe que m() especifica un parámetro Integer. Dentro de main(), m() se pasa el valor int 199. Dado que m() está esperando un Integer, este valor es automáticamente encapsulado. Luego, se llama m2(). Devuelve el valor int 10. Este valor int se asigna a iOb en main(). Como iOb es un Integer, el valor devuelto por m2() es autoboxado. A continuación, se llama m3(). Devuelve un Integer que se desencapsula automáticamente en un int. Finalmente, se llama a Math.sqrt() con iOb como argumento. En este caso, iOb se desencapsula automáticamente y su valor se promueve a double, ya que ese es el tipo esperado por Math.sqrt().

4. Autoboxing/Unboxing se produce en expresiones

En general, el autoboxing y el unboxing tienen lugar siempre que se requiera una conversión a un objeto o desde un objeto. Esto se aplica a las expresiones. Dentro de una expresión, un objeto numérico se desencapsula automáticamente. El resultado de la expresión se vuelve a re-capsular, si es necesario. Por ejemplo, considere el siguiente programa:

// Autoboxing / unboxing ocurre dentro de expresiones.
class Autobox {

    public static void main(String[] args) {
        Integer iOb, iOb2;
        int i;

        iOb=99;
        System.out.println("Valor original de iOb: "+iOb);

        //Lo siguiente desencapsula automáticamente iOb, realiza el incremento,
        //y luego vuelve a re-capsular el resultado en iOb.
        ++iOb;
        System.out.println("Después de ++iOb: "+iOb);

        //Aquí, iOb es desencapsulado, su valor se incrementa en 10,
        //y el resultado es encapsulado y almacenado de nuevo en iOb.
        iOb+=10;
        System.out.println("Después de iOb+=10: "+iOb);

        //Aquí, iOb es desencapsulado, la expresión es evaluada,
        //y el resultado es re-encapsulado y asignado a iOb2.
        iOb2=iOb+(iOb/3);
        System.out.println("iOb2 después de la expresión: "+iOb2);

        //Se evalúa la misma expresión, pero el resultado no se vuelve a encapsular.
        i= iOb+(iOb/3);
        System.out.println("i después de la expresión: "+i);
    }
}

Salida:

Valor original de iOb: 99
Después de ++iOb: 100
Después de iOb+=10: 110
iOb2 después de la expresión: 146
i después de la expresión: 146

En el programa, preste especial atención a esta línea:

++iOb;

Esto hace que el valor en iOb se incremente. Funciona de la siguiente manera: iOb se desencapsula, el valor se incrementa y el resultado se vuelve a encapsular.

Debido al desencapsulado automático, puede usar objetos numéricos enteros, como un Integer, para controlar una declaración switch. Por ejemplo, considera este fragmento:

Integer iOb=2;
switch (iOb){
    case 1:
        System.out.println("uno");
        break;
    case 2:
        System.out.println("dos");
        break;
     default:
         System.out.println("error");
}

Cuando se evalúa la expresión del switch, iOb se desencapsula y se obtiene su valor int.

Como muestran los ejemplos en el programa, debido al autoboxing/unboxing, usar objetos numéricos en una expresión es intuitivo y fácil. Con las primeras versiones de Java, dicho código habría implicado conversiones y llamadas a métodos como intValue().

4.1. Mal uso del autoboxing/unboxing

Debido al autoboxing y el auto-unboxing, uno podría sentirse tentado a usar objetos como Integer o Double exclusivamente, abandonando primitivos por completo. Por ejemplo, con autoboxing/unboxing es posible escribir código como este:

//Un mal uso de autoboxing / auto-unboxing
Double a,b,c;
a=10.2;
b=11.4;
c=12.6;

Double avg=(a+b+c)/3;

En este ejemplo, los objetos de tipo Double contienen valores, que luego se promedian y el resultado asignado a otro objeto Double. Aunque este código es técnicamente correcto y, de hecho, funciona correctamente, es un mal uso del autoboxing/unboxing. Es mucho menos eficiente que el código equivalente escrito usando el tipo primitivo double. La razón es que cada autobox y auto-unbox agrega sobrecarga que no está presente si se usa el tipo primitivo.

En general, debe restringir el uso de los wrappers de tipo solo a aquellos casos en los que se requiere una representación de objeto de tipo primitivo. Autoboxing/unboxing no se agregó a Java como una forma de “puerta trasera” de eliminar los tipos primitivos.

Autoboxing y Unboxing en Java
  • Autoboxing y Unboxing Ejemplos

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.