Icono del sitio Profile Software Services

Clases Wrapper (envoltorio) en Java

Qué son las clases Wrapper en Java

En este artículo os vamos a presentar las clases Wrapper, unos tipos de clase en Java, también son conocidos como envoltorios.  

Tipos de datos

Primeramente y para hacer repaso, los tipos de datos se subdividen en dos:

¿Qué son las clases Wrapper? 

Para todos los tipos de datos primitivos, existen unas clases llamadas Wrapper, también conocidas como envoltorio, ya que proveen una serie de mecanismos que nos permiten envolver a un tipo de dato primitivo permitiéndonos con ello el tratarlos como si fueran objetos.

Tipos de wrappers en Java

Existen 8 tipos primitivos predefinidos en Java, cada uno de los tipos de tipos de datos primitivos tienen asociados su correspondiente clase Wrapper para poder realizar las conversiones de tipos primitivos a objetos (tipos no primitivos).

Tipos primitivos (no son objetos y por tanto no poseen métodos)Wrappers(son objeto y por tanto poseen métodos)
byteByte
shortShort
intInteger
longLong
booleanBoolean
floatFloat
doubleDouble
charCharacter

Wrappers disponibles a partir del JDK 5

Podemos trabajar con Wrappers a partir de JDK 5, por lo que dispondremos de ellos desde dicha versión en adelante. Aunque esto no suele ser un problema, ya que la gran mayoría de proyectos trabajan con versiones de JDK superiores como, por ejemplo, JDK 8.

Si nos metemos en la API de Java, y buscamos el package java.lang, podemos ver que nos aparecen los distintos tipos de Wrappers:

Todos los Wrappers a su vez dependen de Java.lang.Object ya que, como hemos dicho, los Wrappers no dejan de ser objetos y por tanto descienden de la clase Object. Podemos apreciar todo esto de una forma más sencilla a partir del siguiente esquema:

Declarando un tipo primitivo

Un ejemplo de declaración de un tipo de dato primitivo sería:

public class Main {
   public static void main(String[] args) {
       // Declaring a primitive type
       int numPrimitive = 6;
   }
}

Si ahora intentamos hacer un System.out.println(); en el que utilizar sus métodos podemos confirmar lo que veníamos diciendo, que los tipos primitivos no disponen de ningún método al no ser objetos:

Si nos fijamos, podemos ver que lo que nos aparece no son métodos sino expresiones.

Wrapeando vs deswrapeando

Dependiendo del punto sobre el que partimos: tipo de dato primitivo o no primitivo, y por tanto, considerado como un objeto; podemos realizar dos tipos de conversiones.

Wrapeando y deswrapeando con: Autoboxing y/o boxing junto a autounboxing y/o unboxing

Aunque existen otras formas de realizar el autoboxing, el proceso de autoboxing más sencillo sería:

public class Main {
   public static void main(String[] args) {
       Character chWrapped = 'a'; //Autoboxing
       System.out.println(chWrapped.getClass());
   }
}

Vemos que como ya hemos auto convertido el tipo de dato primitivo a su correspondiente Wrapper pasando a ser un objeto, ya podemos utilizar los métodos de este objeto y por ejemplo utilizar el método getClass() con la finalidad de mostrar a qué clase pertenece dicho objeto. Mostrando como resultante que dicho objeto es una instancia (es decir, que se crea a partir de la clase) de la clase Character situada dentro del package java.lang. Concretamente nos mostrará java.lang.Character:

Lo que realmente hace java internamente cuando realizamos una autoboxing es una especie de conversión (casteo) hacía un objeto “similar” a la siguiente: 

public class Main {
   public static void main(String[] args) {
       char ch = 'a';
       System.out.println(((Object) ch).getClass());
   }
}

Aunque siendo puristas la conversión sería hacía una clase Wrapper correspondiente a dicho tipo de objeto. Por ejemplo, en el caso de tipo de dato char a su Wrapper Character. Vamos a verlo:

public class Main {
   public static void main(String[] args) {
       char ch = 'a';
       System.out.println(((Character) ch).getClass());
   }
}

Con la que obtendremos el mismo resultado:

Si intentamos ahora sacar los métodos podremos observar que al ser un tipo primitivo ya no están disponibles.

Un ejemplo podría ser:

public class Main {
   public static void main(String[] args) {
       int numPrimitive = 6;
       Integer numWrapper = Integer.valueOf(numPrimitive);
       System.out.println(numWrapper.getClass().getName() + " ¿Es un objeto? " + (numWrapper instanceof Object));
   }
}

Un ejemplo podría ser:

public class Main {
   public static void main(String[] args) {
       int numPrimitive = 6;
       Integer numWrapper = Integer.valueOf(numPrimitive);
       System.out.println(numWrapper.getClass().getName() + " ¿Es un objeto? " + (numWrapper instanceof Object));
       int numUnWrapped = numWrapper.intValue();
       System.out.println(numUnWrapped);
   }
}

Si el proceso transforma un tipo de dato primitivo hacía su clase envoltura se conoce como embalaje, en cambio si el proceso transforma un objeto de tipo Wrapped a un tipo primitivo se conoce como desembalaje.

Problemas de utilizar el constructor de las clases Wrapper para realizar el autoboxing

Una de las maneras que podemos encontrar para hacer autoboxing es mediante el constructor de la clase Wrapper. Vamos a ver un ejemplo:

public class Main {
   public static void main(String[] args) {
       // Declaring a primitive type
       int numPrimitive = 6;
       // Old form to declare a Wrapper (deprecated)
       Integer numWrapperDeprecatedFormI = new Integer(numPrimitive); // Using the primitive type
       Integer numWrapperDeprecatedFormII = new Integer(55); // Using a direct value
   }
}

El problema con el que nos encontramos cada vez más usualmente es que desde la versión 9 de JDK, esta forma envolver automáticamente un tipo primitivo está deprecada. En mi caso en particular, como estoy trabajando sobre la versión 16 de JDK y, por tanto, dicha versión es superior a JDK 9, el IDE nos mostrará una advertencia mostrándonos que próximamente se eliminará dicha forma de autowrapear un valor. Vamos a verlo:

Actualmente, tal y como nos recomienda el IDE desde la propia advertencia, es aconsejable el utilizar el método valueOf() para introducir un valor dentro de un objeto wrapper. 

Pese a ello, si no seguimos la recomendación del IDE y seguimos haciendo el autoWrapper desde el constructor, podemos ver que, por el momento, pese a ello aún está operativo y por tanto, lo podríamos usar y utilizar sus métodos:

Y por ejemplo utilizar varios métodos entre los que se incluye el método compareTo que nos permite comparar dos valores para ver si son iguales o distintos:

public class Main {
   public static void main(String[] args) {
       // Declaring a primitive type
       int numPrimitive = 6;

       // Old form to declare a Wrapper (deprecated)
       Integer numWrapperDeprecatedFormI = new Integer(numPrimitive); // Using the primitive type
       Integer numWrapperDeprecatedFormII = new Integer(55); // Using a direct value

       // Print data
       System.out.println(numWrapperDeprecatedFormI.compareTo(6)); // Print 0 → When the value to compare it's the same
       System.out.println(numWrapperDeprecatedFormI.compareTo(1)); // Print 1 → When the value to compare it's lower than numWrapper
       System.out.println(numWrapperDeprecatedFormI.compareTo(33)); // Print 1 → When the value to compare it's bigger than numWrapper
   }
}

Obteniendo como resultado el siguiente:

Transformado el wrapeo anterior (constructor) a una envoltura con valueOf

Como ya hemos visto en la advertencia cuando hemos trabajado con Integer numWrapperDeprecatedFormII = new Integer(55); con Wrappers, actualmente la forma correcta de realizar un Wrapper sería mediante el uso de valueOf. Vamos a ver un ejemplo:

public class Main {
   public static void main(String[] args) {
       // Declaring a primitive type
       int numPrimitive = 6;
       Integer numWrapperFormI = Integer.valueOf(numPrimitive); // Using the primitive type
       Integer numWrapperFormII = Integer.valueOf(55); // Using a direct value
   }
}

Si trabajamos con valueOf, es muy importante que no tengamos el new en la asignación del valor del Integer ya que sino nos daría un error.

Un ejemplo algo más avanzado respecto al ejemplo anterior podría ser el siguiente:  

public class Main {
   public static void main(String[] args) {
       // Declaring a primitive type
       int numI = 6;
       // Print Data
       Main m = new Main();
       m.checkNumber(numI, 6); // Comparte 6 (numI value) vs 6 and print 0
       m.checkNumber(numI, 1); // Comparte 6 (numI value) vs 1 and print 1
       m.checkNumber(numI, 33); // Comparte 6 (numI value) vs 33 and print -33
   }

   private static void checkNumber(int numberToWrapper, int numberToCheck) {
       Integer numWrapperDeprecatedFormI = Integer.valueOf(numberToWrapper); // Using the primitive data type Form I
       if (numWrapperDeprecatedFormI.compareTo(numberToCheck) == 0) {
           System.out.println(numberToWrapper + " = " + numberToCheck + " y el compareTo devolverá " + numWrapperDeprecatedFormI.compareTo(numberToCheck));
       } else if (numWrapperDeprecatedFormI.compareTo(numberToCheck) == 1) {
           System.out.println(numberToWrapper + " > " + numberToCheck + " y el compareTo devolverá " + numWrapperDeprecatedFormI.compareTo(numberToCheck));
       } else if (numWrapperDeprecatedFormI.compareTo(numberToCheck) == -1) {
           System.out.println(numberToWrapper + " < " + numberToCheck + " y el compareTo devolverá " + numWrapperDeprecatedFormI.compareTo(numberToCheck));
       }
   }
}

El resultado será:

¿Por qué no trabajamos directamente con Wrappers?

Los primitivos han estado presentes desde los orígenes de Java en 1996.  A día de hoy los tipos primitivos siguen utilizándose principalmente por el notable rendimiento que ofrecen. 

Aunque es un tema que ha generado una gran polémica principalmente por los detractores de los tipos primitivos como, por ejemplo, uno de los más famosos, Simon Ritter quien quería eliminarlos en el lanzamiento de la versión 10 de JDK desde hace tiempo.

Fuente Slideshare Keynote to Java SE 8 beyond Simon Ritter

Simon Ritter – Fuente Oracle

Simon se unió a SUN Microsystems en 1996 siendo uno de los responsables de promover el uso de las tecnologías de SUN (entre las que se incluye Java) y que más tarde, tras la absorción de SUN por parte de Oracle, pasó a dirigir el proyecto Java Evangelism pero pese a su peso en la compañía no consiguió eliminar los tipos primitivos.

El motivo por el cual siguen siendo usados y que les ha permitido sobrevivir durante tantos años es simple, el rendimiento. Los tipos primitivos no contienen métodos lo que supone un enorme beneficio en lo referente a su rendimiento. Por ejemplo en una aplicación que 

Conclusión

Esto es todo, espero que os haya gustado tanto como a mí el conocer un poco más a fondo el mundo de las clases de envoltorios en Java clases, envoltura de primitivos, o Wrapper Classes. 

Si quieres seguir profundizando tus conocimientos sobre Java, no te pierdas este artículo en el que diseccionamos los mejores frameworks de Java en la actualidad y este otro en el que recopilamos las librerías Java más interesantes.

Descubre mucho más en nuestro blog y canal de YouTube. ¡Suscríbete!

Salir de la versión móvil