En este artículo os vamos a presentar las clases Wrapper, unos tipos de clase en Java, también son conocidos como envoltorios.
Qué vas as ver en esta entrada
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) |
byte | Byte |
short | Short |
int | Integer |
long | Long |
boolean | Boolean |
float | Float |
double | Double |
char | Character |
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
- 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:
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:
- 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.
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!