ejemplos - ¿Por qué usamos autoboxing y unboxing en Java?
ejemplos de wrappers en java (9)
Autoboxing es la conversión automática que realiza el compilador de Java entre los tipos primitivos y sus correspondientes clases de contenedor de objetos. Por ejemplo, convertir un int a un entero, un doble a un doble, etc. Si la conversión es al revés, esto se llama unboxing.
Entonces, ¿por qué lo necesitamos y por qué utilizamos autoboxing y unboxing en Java?
Algunas estructuras de datos pueden aceptar solo objetos, no tipos primitivos.
Ejemplo: la clave en un HashMap.
Vea esta pregunta para más información: HashMap e int como clave
Hay otras buenas razones, como un campo "int" en una base de datos, que también podría ser NULL. Un int en Java no puede ser nulo; una referencia entera puede. Autoboxing y unboxing proporcionan una función para evitar escribir código extraño en las conversiones de un lado a otro.
ArrayList no admite tipos primitivos, solo admite clase. pero necesitamos usar tipos primitivos, por ejemplo, int, double, etc.
ArrayList<String> strArrayList = new ArrayList<String>(); // is accepted.
ArrayList<int> intArrayList = new ArrayList<int>(); // not accepted.
La clase Integer envuelve un valor del tipo primitivo int en un objeto, por lo que se acepta el siguiente código.
ArrayList<Integer> intArrayList = new ArrayList<Integer>(); // is accepted.
podemos agregar un valor con el método add (value). Para agregar un valor de cadena diga "Hola" en el código strArrayList es solo
strArrayList.add("Hello");
y agregue un valor int digamos 54 podemos escribir
intArrayList.add(54);
pero cuando escribimos intArrayList.add (54); compilador convertir a la siguiente línea
intArrayList.add(Integer.valueOf(54));
Como intArrayList.add (54) es fácil y más aceptable desde el lado del usuario, el compilador hace el trabajo duro que es `
intArrayList.add(Integer.valueOf(54));
es autoBoxing.
De manera similar, para recuperar el valor, simplemente escribimos intArrayList.get (0) y el compilador convierte a
<code>intArrayList.get(0).intValue();
que es autoUnboxing.
Autoboxing: Convertir un valor primitivo en un objeto de la clase de contenedor correspondiente.
Unboxing: convertir un objeto de un tipo de contenedor a su valor primitivo correspondiente
// Java program to illustrate the concept
// of Autoboxing and Unboxing
import java.io.*;
class GFG
{
public static void main (String[] args)
{
// creating an Integer Object
// with value 10.
Integer i = new Integer(10);
// unboxing the Object
int i1 = i;
System.out.println("Value of i: " + i);
System.out.println("Value of i1: " + i1);
//Autoboxing of char
Character gfg = ''a'';
// Auto-unboxing of Character
char ch = gfg;
System.out.println("Value of ch: " + ch);
System.out.println("Value of gfg: " + gfg);
}
}
Comenzando con JDK 5, Java ha agregado dos funciones importantes: autoboxing y autounboxing. AutoBoxing es el proceso para el cual un tipo primitivo se encapsula automáticamente en el contenedor equivalente cada vez que se necesita dicho objeto. No tiene que construir explícitamente un objeto. El desempaquetado automático es el proceso mediante el cual el valor de un objeto encapsulado se extrae automáticamente de un contenedor de tipos cuando se requiere su valor. No necesita llamar a un método como intValue () o doubleValue () .
La adición de autoboxing y auto-unboxing simplifica enormemente los algoritmos de escritura , eliminando el cebo manualmente en el boxeo y unboxing de valores. También es útil para evitar errores . También es muy importante para los genéricos , que solo operan con objetos. Por último, el autoboxing facilita el trabajo con el marco de colecciones .
Porque son de diferentes tipos, y como conveniencia. Es probable que el rendimiento sea la razón de tener tipos primitivos.
Se requiere cierto contexto para comprender completamente la razón principal detrás de esto.
Primitivas versus clases
Las variables primitivas en Java contienen valores
(un número entero, un número binario de coma flotante de doble precisión, etc.).
Debido a que
estos valores pueden tener diferentes longitudes
, las variables que los contienen también pueden tener diferentes longitudes (considere
float
versus
double
).
Por otro lado,
las variables de clase contienen referencias
a instancias.
Las referencias se implementan típicamente como punteros (o algo muy similar a los punteros) en muchos idiomas.
Estas cosas suelen tener el mismo tamaño, independientemente de los tamaños de las instancias a las que se refieren (
Object
,
String
,
Integer
, etc.).
Esta propiedad de las variables de clase
hace que las referencias que contienen sean intercambiables
(hasta cierto punto).
Esto nos permite hacer lo que llamamos
sustitución
: en términos generales,
usar una instancia de un tipo particular como una instancia de otro tipo relacionado
(use una
String
como un
Object
, por ejemplo).
Las variables primitivas no son intercambiables
de la misma manera, ni entre sí ni con
Object
.
La razón más obvia para esto (pero no la única razón) es su diferencia de tamaño.
Esto hace que los tipos primitivos sean inconvenientes a este respecto, pero aún los necesitamos en el lenguaje (por razones que se reducen principalmente al rendimiento).
Genéricos y tipo borrado
Los tipos genéricos son tipos con uno o más
parámetros de tipo
(el número exacto se llama
aridad genérica
).
Por ejemplo, la
definición de tipo genérico
List<T>
tiene un parámetro de tipo
T
, que puede ser
Object
(que produce un
tipo concreto
List<Object>
),
String
(
List<String>
),
Integer
(
List<Integer>
), etc. .
Los tipos genéricos son mucho más complicados que los no genéricos.
Cuando se les presentó Java (después de su lanzamiento inicial), para evitar hacer cambios radicales en la JVM y posiblemente romper la compatibilidad con binarios más antiguos,
los creadores de Java decidieron implementar tipos genéricos de la manera menos invasiva:
todos los tipos concretos de
List<T>
, de hecho, se compila en (el equivalente binario de)
List<Object>
(para otros tipos, el límite puede ser algo distinto de
Object
, pero se obtiene el punto).
La información de parámetros de tipo y arity genérico se pierde en este proceso
, por lo que lo llamamos
borrado de tipo
.
Poniendo los dos juntos
Ahora el problema es la combinación de las realidades anteriores: si
List<T>
convierte en
List<Object>
en todos los casos, entonces
T
siempre debe ser un tipo que pueda asignarse directamente a
Object
.
Cualquier otra cosa no se puede permitir.
Como, como dijimos antes,
int
,
float
y
double
no son intercambiables con
Object
, no puede haber una
List<int>
,
List<float>
o
List<double>
(a menos que exista una implementación significativamente más complicada de genéricos en la JVM)
Pero Java ofrece tipos como
Integer
,
Float
y
Double
que envuelven estas primitivas en instancias de clase, haciéndolas efectivamente sustituibles como
Object
,
permitiendo
así
que los tipos genéricos también trabajen indirectamente con las primitivas
(porque
puede
tener
List<Integer>
,
List<Float>
,
List<Double>
y así sucesivamente).
El proceso de crear un
Integer
partir de un
int
, un
Float
partir de un
float
etc., se denomina
boxing
.
El reverso se llama
unboxing
.
Debido a que tener que encajonar primitivas cada vez que desea usarlas como
Object
es inconveniente,
hay casos en los que el lenguaje hace esto automáticamente,
eso se llama
autoboxing
.
¿Por qué tenemos (des) boxeo?
para hacer que escribir código donde mezclemos primitivas y sus alternativas orientadas a objetos (OO) sea más cómodo / menos detallado.
¿Por qué tenemos primitivas y sus alternativas OO?
los tipos primitivos no son clases (a diferencia de C #), por lo tanto, no son subclases de
Object
y no se pueden anular.
tenemos primitivas como
int
por razones de rendimiento, y las alternativas
Object
como
Integer
para los beneficios de la programación OO, y como un punto menor, para tener una buena ubicación para las constantes y métodos de utilidad (Integer.MAX_VALUE e
Integer.toString(int)
) .
Los beneficios OO son visibles más fácilmente con los genéricos (
List<Integer>
), pero no se limitan a eso, por ejemplo:
Number getMeSome(boolean wantInt) {
if (wantInt) {
return Integer.MAX_VALUE;
} else {
return Long.MAX_VALUE;
}
}
Auto Boxing se usa para convertir tipos de datos primitivos a sus objetos de clase wrapper. La clase de envoltura proporciona una amplia gama de funciones para realizar en los tipos primitivos. El ejemplo más común es:
int a = 56;
Integer i = a; // Auto Boxing
Es necesario porque los programadores pueden escribir código directamente y JVM se encargará del encajonamiento y desempaquetado.
El Auto Boxing también es útil cuando trabajamos con tipos java.util.Collection. Cuando deseamos crear una Colección de tipos primitivos, no podemos crear directamente una Colección de un tipo primitivo, solo podemos crear una Colección de objetos. Por ejemplo :
ArrayList<int> al = new ArrayList<int>(); // not supported
ArrayList<Integer> al = new ArrayList<Integer>(); // supported
al.add(45); //auto Boxing
Clases de envoltura
Cada uno de los 8 tipos primitivos de Java (byte, short, int, float, char, double, boolean, long) tiene una clase Wrapper separada asociada con ellos. Estas clases Wrapper tienen métodos predefinidos para realizar operaciones útiles en tipos de datos primitivos.
Uso de clases de envoltura
String s = "45";
int a = Integer.parseInt(s); // sets the value of a to 45.
Hay muchas funciones útiles que proporcionan las clases Wrapper. Echa un vistazo a los documentos de Java here
Unboxing es opuesto a Auto Boxing, donde convertimos el objeto de clase wrapper nuevamente a su tipo primitivo. JVM hace esto automáticamente para que podamos usar las clases de contenedor para ciertas operaciones y luego convertirlas nuevamente a tipos primitivos, ya que las primitivas resultan en un procesamiento más rápido. Por ejemplo :
Integer s = 45;
int a = s; auto UnBoxing;
En el caso de colecciones que funcionan con objetos, solo se utiliza el desempaquetado automático. Así es cómo :
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(45);
int a = al.get(0); // returns the object of Integer . Automatically Unboxed .
Los tipos primitivos (no objeto) tienen su justificación en eficiencia.
Los tipos primitivos
int, boolean, double
son datos inmediatos, mientras que
Object
s son referencias.
De ahí los campos (o variables)
int i;
double x;
Object s;
necesitaría memoria local 4 + 8 + 8? donde para el objeto solo se almacena la referencia (dirección) a la memoria.
Usando los contenedores de objetos
Integer, Double
y otros, uno introduciría una indirecta, referencia a alguna instancia de Integer / Double en la memoria del montón.
¿Por qué se necesita el boxeo?
Esa es una cuestión de alcance relativo.
En un futuro Java, se planea poder tener una
ArrayList<int>
, levantando tipos primitivos.
Respuesta: Por ahora, una ArrayList solo funciona para Object, reservando espacio para una referencia de objeto y gestionando la recolección de basura de la misma manera. Por lo tanto, los tipos genéricos son objetos secundarios. Entonces, si se desea una ArrayList de valores de punto flotante, se necesita ajustar un doble en un objeto Doble.
Aquí Java difiere del C ++ tradicional con sus plantillas: las clases C ++
vector<string>, vector<int>
crearían dos productos de compilación.
El diseño de Java fue para tener una ArrayList.class, no necesitando para cada tipo de parámetro un nuevo producto compilado.
Entonces, sin encajonar a Object, uno necesitaría compilar clases para cada aparición de un tipo de parámetro. En concreto: cada colección o clase de contenedor necesitaría una versión para Object, int, double, boolean. La versión para Object manejaría todas las clases secundarias.
De hecho, la necesidad de tal diversificación ya existía en Java SE para IntBuffer, CharBuffer, DoubleBuffer, ... que operan en int, char, double. Se resolvió de una manera hacky generando estas fuentes a partir de una fuente común.