Java Language
Autoboxing
Поиск…
Вступление
Autoboxing - это автоматическое преобразование, которое компилятор Java делает между примитивными типами и соответствующими классами обертки объектов. Пример, преобразование int -> Integer, double -> Double ... Если преобразование идет другим путем, это называется распаковкой. Как правило, это используется в коллекциях, которые не могут содержать объекты, отличные от объектов, где необходимы примитивные типы бокса, прежде чем устанавливать их в коллекции.
замечания
Автобоксирование может иметь проблемы с производительностью при частом использовании в вашем коде.
Использование int и Integer взаимозаменяемо
Поскольку вы используете общие типы с классами утилит, вы часто можете обнаружить, что типы номеров не очень полезны при указании в качестве типов объектов, поскольку они не равны их примитивным аналогам.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
К счастью, выражения, которые оценивают int
могут использоваться вместо Integer
когда это необходимо.
for (int i = 0; i < 10; i++)
ints.add(i);
ints.add(i);
утверждение эквивалентно:
ints.add(Integer.valueOf(i));
И сохраняет свойства из значения Integer#valueOf
например, с теми же Integer
объектами, которые кешируются JVM, когда он находится в диапазоне кеширования чисел.
Это также относится к:
-
byte
иByte
-
short
иShort
-
float
иFloat
-
double
иDouble
-
long
иLong
-
char
иCharacter
-
boolean
иBoolean
Однако следует проявлять осторожность в неоднозначных ситуациях. Рассмотрим следующий код:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
Интерфейс java.util.List
содержит как метод remove(int index)
(метод интерфейса List
), так и remove(Object o)
(метод, унаследованный от java.util.Collection
). В этом случае бокс не происходит и не вызывается remove(int index)
.
Еще один пример странного поведения кода Java, вызванного автобоксированием Целые числа со значениями в диапазоне от -128
до 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
Это происходит потому, что >=
оператор неявно вызывает intValue()
который возвращает int
while ==
сравнивает ссылки , а не значения int
.
По умолчанию Java кэширует значения в диапазоне [-128, 127]
, поэтому оператор ==
работает, потому что Integers
в этом диапазоне ссылаются на одни и те же объекты, если их значения одинаковы. Максимальное значение кэшируемого диапазона можно определить с -XX:AutoBoxCacheMax
опции -XX:AutoBoxCacheMax
JVM. Итак, если вы запустите программу с помощью -XX:AutoBoxCacheMax=1000
, следующий код напечатает true
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Использование Boolean в выражении if
Из-за автоматического распаковки в операторе if
можно использовать Boolean
выражение:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Это работает while
, do while
и условие в операторах for
.
Обратите внимание, что если Boolean
равно null
, в преобразовании будет NullPointerException
.
Автоматическая распаковка может привести к NullPointerException
Этот код компилирует:
Integer arg = null;
int x = arg;
Но во время выполнения он будет сбой при исключении java.lang.NullPointerException
во второй строке.
Проблема в том, что примитивный int
не может иметь null
значение.
Это минималистический пример, но на практике он часто проявляется в более сложных формах. NullPointerException
не очень интуитивно понятное и часто мало помогает в поиске таких ошибок.
Положитесь на автобоксинг и автоматическую распаковку с осторожностью, убедитесь, что значения unboxed не будут иметь null
значений во время выполнения.
Память и вычислительные накладные расходы на автобоксинг
Автобоксинг может иметь значительные накладные расходы памяти. Например:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
как правило, потребляют значительный объем памяти (около 60 кб для 6 тыс. фактических данных).
Кроме того, целые числа в штучной упаковке обычно требуют дополнительных округлений в памяти и, таким образом, делают кэширование процессора менее эффективным. В приведенном выше примере доступ к памяти распространяется на пять разных местоположений, которые могут находиться в совершенно разных областях памяти: 1. объект HashMap
, 2. объект Entry[] table
, 3. объект Entry
, 4. элемент ввода key
слов (бокс примитивного ключа), 5. объект value
entry (бокс примитивного значения).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
Чтение в boxed
требует двух обращений к памяти, доступ к primitive
только один.
При получении данных с этой карты, казалось бы, невинный код
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
эквивалентно:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
Как правило, приведенный выше код вызывает сбор и сбор мусора объекта Integer
для каждой операции Map#get(Integer)
. (Подробнее см. Примечание ниже).
Чтобы уменьшить эти накладные расходы, несколько библиотек предлагают оптимизированные коллекции для примитивных типов, которые не требуют бокса. В дополнение к тому, чтобы избежать накладных расходов на бокс, для этой коллекции потребуется примерно 4 раза меньше памяти на запись. В то время как Java Hotspot может оптимизировать автобоксинг, работая с объектами в стеке вместо кучи, невозможно оптимизировать накладные расходы памяти и вызванную память.
В потоках Java 8 также есть оптимизированные интерфейсы для примитивных типов данных, таких как IntStream
которые не требуют бокса.
Примечание: типичная среда выполнения Java поддерживает простой кэш Integer
и другой примитивный объект-оболочку, который используется фабричными методами valueOf
и автобоксированием. Для Integer
диапазон по умолчанию этого кеша составляет от -128 до +127. Некоторые JVM предоставляют параметр командной строки JVM для изменения размера и диапазона кеша.
Различные случаи Когда Integer и int могут использоваться взаимозаменяемо
Случай 1: используется вместо аргументов метода.
Если метод требует, чтобы объект класса-оболочки был аргументом. Затем, в качестве взаимозаменяемого аргумента может быть передана переменная соответствующего примитивного типа и наоборот.
Пример:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Случай 2: При передаче возвращаемых значений:
Когда метод возвращает примитивную переменную типа, тогда объект соответствующего класса-оболочки может быть передан как возвращаемое значение взаимозаменяемо и наоборот.
Пример:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Случай 3: При выполнении операций.
Всякий раз, когда выполняются операции над числами, переменная примитива и объект соответствующего класса-оболочки могут использоваться взаимозаменяемо.
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
Pitfall : не забудьте инициализировать или присвоить значение объекту класса-оболочки.
При использовании объекта класса-оболочки и примитивной переменной взаимозаменяемо никогда не забывать или пропустить инициализацию или присвоение значения объекту класса-оболочки иначе он может привести к исключению нулевого указателя во время выполнения.
Пример:
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
}
В приведенном выше примере значение объекта не назначено и неинициализировано, и, таким образом, во время выполнения программа будет запущена в исключение нулевого указателя. Так же, как ясно из приведенного выше примера, значение объекта никогда не должно оставаться неинициализированным и неназначенным.