¡Compártelo!

Clases Wrapper (envoltorio) 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:

  • Primitivos: son los únicos elementos de todo el lenguaje que no son considerados como objetos (y por tanto, no tienen métodos).
  • No primitivos: al no ser tipos primitivos, son considerados como objetos (y por tanto, tiene métodos).

¿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:

clases wrapper

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:

clases wrapper

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:

clases wrapper

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.

clases wrapper

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

  • Autoboxing (wraping automático): es el proceso de conversión automática que realiza el compilador de Java para que un tipo primitivo pase a ser un objeto utilizando para ello su clase de envoltura (“Wrapper”).

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:

clases wrapper

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());
   }
}
clases wrapper

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:

clases wrapper
  • Auto Unboxing (deswraping automático): es el proceso de conversión automática que realiza el compilador de Java para que un objeto de clave Wrapper pase a ser un tipo primitivo perdiendo con ello sus métodos.

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

  • Boxing (wraping manual): el proceso de conversión no automático que realizamos con el fin de pasar un tipo primitivo a un objeto mediante su clase de envoltura (“Wrapper”).

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));
   }
}

  • Unboxing (deswraping manual): el proceso de conversión no automático que realizamos con el fin de pasar un tipo no primitivo (Wrapper) a un tipo primitivo.

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!

Artículos relacionados

No code

Qué es el No Code: Principales herramientas

La capacidad de crear soluciones tecnológicas sin la necesidad de escribir código se ha convertido en una tendencia cada vez más relevante. Esto se debe en gran parte al surgimiento de herramientas No Code, que permiten a personas con diversos niveles de habilidad técnica dar

Object Pooling

Patrones de diseño en los videojuegos: Object Pooling

El uso de patrones de diseño, como el Object Pooling, es una práctica muy recomendable cuando se quieren realizar desarrollos de software escalables, robustos y que tengan un buen rendimiento. Además, nos puede ayudar a mantener una estructuración de todo el código fuente para ayudar

jdk 21

Jdk 21: mejoras en la última versión LTS de Java

Cada 6 meses Java lanza una nueva versión de nuestro lenguaje favorito. Da igual si la estábamos esperando con ganas o si nos pilla por sorpresa, es algo que celebrar dentro de la comunidad. Esta vez la versión 21 incluye diferentes características estables, otras en