Java Language
Autoboxing
Buscar..
Introducción
Autoboxing es la conversión automática que realiza el compilador de Java entre los tipos primitivos y sus correspondientes clases de envoltorios de objetos. Ejemplo, conversión de int -> Entero, doble -> Doble ... Si la conversión es a la inversa, esto se llama unboxing. Por lo general, esto se usa en Colecciones que no pueden contener más que Objetos, donde se necesitan tipos primitivos de boxeo antes de configurarlos en la colección.
Observaciones
Autoboxing puede tener problemas de rendimiento cuando se utiliza con frecuencia en su código.
Usando int y entero indistintamente
A medida que usa tipos genéricos con clases de utilidad, a menudo puede encontrar que los tipos de números no son muy útiles cuando se especifican como tipos de objetos, ya que no son iguales a sus contrapartes primitivas.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Afortunadamente, las expresiones que evalúan a int
pueden usarse en lugar de un Integer
cuando sea necesario.
for (int i = 0; i < 10; i++)
ints.add(i);
El ints.add(i);
declaración es equivalente a:
ints.add(Integer.valueOf(i));
Y retiene las propiedades de Integer#valueOf
, como tener los mismos objetos de Integer
almacenados en caché por la JVM cuando está dentro del rango de almacenamiento en caché de números.
Esto también se aplica a:
-
byte
yByte
-
short
yShort
-
float
yFloat
-
double
yDouble
-
long
yLong
-
char
yCharacter
-
boolean
yBoolean
Se debe tener cuidado, sin embargo, en situaciones ambiguas. Considere el siguiente código:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
La interfaz java.util.List
contiene tanto un remove(int index)
(Método de interfaz de List
) como un remove(Object o)
(método heredado de java.util.Collection
). En este caso no se lleva a cabo ningún boxeo y se llama a la remove(int index)
.
Un ejemplo más del extraño comportamiento del código Java causado por los enteros automoxing con valores en el rango de -128
a 127
:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); // true
System.out.println(c <= d); // true
System.out.println(c >= d); // true
System.out.println(c == d); // false
Esto sucede porque el operador >=
llama implícitamente a intValue()
que devuelve int
mientras que ==
compara las referencias , no los valores de int
.
De forma predeterminada, Java almacena en caché los valores en el rango [-128, 127]
, por lo que el operador ==
funciona porque los Integers
en este rango hacen referencia a los mismos objetos si sus valores son los mismos. El valor máximo del rango -XX:AutoBoxCacheMax
se puede definir con la -XX:AutoBoxCacheMax
JVM. Por lo tanto, si ejecuta el programa con -XX:AutoBoxCacheMax=1000
, el siguiente código se imprimirá true
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Usando Boolean en la sentencia if
Debido al auto-boxeo, uno puede usar un Boolean
en una sentencia if
:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Eso funciona para while
, do while
y la condición en las declaraciones for
también.
Tenga en cuenta que, si el valor Boolean
es null
, se lanzará una NullPointerException
en la conversión.
Auto-unboxing puede llevar a NullPointerException
Este código compila:
Integer arg = null;
int x = arg;
Pero se bloqueará en tiempo de ejecución con una java.lang.NullPointerException
en la segunda línea.
El problema es que un int
primitivo no puede tener un valor null
.
Este es un ejemplo minimalista, pero en la práctica a menudo se manifiesta en formas más sofisticadas. La NullPointerException
no es muy intuitiva y suele ser de poca ayuda para localizar estos errores.
Confíe en el autoboxing y el auto-boxeo con cuidado, asegúrese de que los valores unboxed no tengan valores null
en el tiempo de ejecución.
Memoria y sobrecarga computacional de Autoboxing
Autoboxing puede venir en una sobrecarga de memoria sustancial. Por ejemplo:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
normalmente consumirá una cantidad sustancial de memoria (alrededor de 60kb para 6k de datos reales).
Además, los enteros en caja generalmente requieren viajes de ida y vuelta adicionales en la memoria, y por lo tanto hacen que los cachés de CPU sean menos efectivos. En el ejemplo anterior, la memoria a la que se accede se extiende a cinco ubicaciones diferentes que pueden estar en regiones completamente diferentes de la memoria: 1. el objeto HashMap
, 2. el objeto de Entry[] table
del mapa, 3. el objeto Entry
, 4. el objeto objeto key
entradas (boxeo de la clave primitiva), 5. objeto de value
entradas (boxeo del valor primitivo).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
La lectura en boxed
requiere dos accesos de memoria, accediendo solo a uno primitive
.
Al obtener datos de este mapa, el código aparentemente inocente
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
es equivalente a:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
Normalmente, el código anterior causa la creación y recolección de elementos no utilizados de un objeto Integer
para cada operación Map#get(Integer)
. (Vea la Nota a continuación para más detalles).
Para reducir esta sobrecarga, varias bibliotecas ofrecen colecciones optimizadas para tipos primitivos que no requieren boxeo. Además de evitar la sobrecarga del boxeo, esta colección requerirá aproximadamente 4 veces menos memoria por entrada. Si bien Java Hotspot puede optimizar el autoboxing al trabajar con objetos en la pila en lugar del montón, no es posible optimizar la sobrecarga de memoria y la indirección de la memoria resultante.
Las transmisiones Java 8 también tienen interfaces optimizadas para tipos de datos primitivos, como IntStream
que no requieren boxeo.
Nota: un tiempo de ejecución de Java típico mantiene un caché simple de Integer
y otro objeto de envoltorio primitivo que utilizan los métodos de fábrica valueOf
, y mediante autofijación. Para Integer
, el rango predeterminado de este caché es de -128 a +127. Algunas JVM proporcionan una opción de línea de comandos JVM para cambiar el tamaño / rango de la memoria caché.
Casos diferentes cuando Integer e int pueden usarse indistintamente
Caso 1: Mientras se usa en lugar de argumentos de método.
Si un método requiere un objeto de la clase envoltura como argumento. Luego, indistintamente, se le puede pasar una variable del tipo primitivo respectivo y viceversa.
Ejemplo:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Caso 2: Mientras se pasan valores de retorno:
Cuando un método devuelve una variable de tipo primitivo, entonces se puede pasar un objeto de la clase envoltura correspondiente como valor de retorno de manera intercambiable y viceversa.
Ejemplo:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Caso 3: Al realizar operaciones.
Siempre que se realicen operaciones en números, la variable de tipo primitivo y el objeto de la respectiva clase envoltura se pueden usar indistintamente.
int i=5;
Integer j=new Integer(7);
int k=i+j;//Is a valid statement
Integer m=i+j;//Is also a valid statement
Trampa : recuerde inicializar o asignar un valor a un objeto de la clase contenedora.
Si bien el uso de un objeto de la clase de envoltorio y la variable primitiva no se olvida, nunca se olvida o se pierde para inicializar o asignar un valor al objeto de la clase de envoltorio, de lo contrario, puede provocar una excepción de puntero nulo en el tiempo de ejecución.
Ejemplo:
public class Test{
Integer i;
int j;
public void met()
{j=i;//Null pointer exception
SOP(j);
SOP(i);}
public static void main(String[] args)
{Test t=new Test();
t.go();//Null pointer exception
}
En el ejemplo anterior, el valor del objeto no está asignado ni inicializado y, por lo tanto, en tiempo de ejecución, el programa ejecutará una excepción de puntero nulo. Por lo tanto, el valor del objeto nunca debe dejarse sin inicializar ni asignar.