¡Compártelo!
Share on facebook
Share on twitter
Share on linkedin

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 nuestro blog y canal de YouTube. ¡Suscríbete!

Artículos relacionados

Día del programador

Día del programador: resolviendo el enigma

En este artículo vamos a explicar cómo resolver el reto que nos propuso Profile con motivo del día del programador, así como algunas curiosidades sobre esta celebración anual. ¿Por qué se celebra el día del programador? Me gustaría aprovechar la ocasión para comentaros el motivo

qué es el testing de software

Qué es el testing de software

En el proceso de desarrollo de software es normal encontrar errores. Cuando esto sucede en la etapa de prueba de software, no supone un gran inconveniente. Continuar sin abordarlos puede generar problemas graves para todas las partes involucradas en el proceso de desarrollo del proyecto.

qué es Docker

¿Qué es Docker y para qué sirve?

Una vez introducidos en el mundo de los contenedores de software y las principales diferencias entre trabajar con contenedores y máquinas virtuales, hablaremos  en este artículo sobre qué es Docker y cómo funciona en detalle. Además,  levantaremos un container a partir de una pequeña API