Автобоксинг и анбоксинг (Java 5+)
В Java есть две параллельные «вселенные» числовых типов:
Примитивы:
byte,short,int,long,float,double,char,boolean— лежат на стеке, не имеют методов, не могут бытьnull.Обёртки (wrapper classes):
Byte,Short,Integer,Long,Float,Double,Character,Boolean— настоящие объекты в куче, могут бытьnull, нужны для коллекций и generics.
С Java 5 компилятор сам вставляет преобразования между ними:
Autoboxing — примитив автоматически заворачивается в обёртку:
Integer x = 5;Unboxing — обёртка автоматически разворачивается в примитив:
int y = new Integer(5);
Минимальная версия: Java 5.
Синтаксис
Integer boxed = 42; // autoboxing: int -> Integer
int unboxed = boxed; // unboxing: Integer -> int
// Вручную это выглядело бы так:
// Integer boxed = Integer.valueOf(42);
// int unboxed = boxed.intValue();
Зачем нужно
Коллекции (
List,Map) хранят только объекты — без обёртокList<int>написать нельзя.Generics не работают с примитивами:
Optional<int>не существует, нуженOptional<Integer>.Удобство:
map.put("speed", 150);без явногоInteger.valueOf(150).
Пример 1. Базовый автобоксинг
public class BoxingBasic {
public static void main(String[] args) {
// int -> Integer
Integer boxed = 42;
// Integer -> int
int unboxed = boxed;
System.out.println("boxed: " + boxed + " (тип Integer)");
System.out.println("unboxed: " + unboxed + " (тип int)");
// Арифметика с обёртками: компилятор unbox-ит, считает, box-ит обратно
Integer a = 10;
Integer b = 20;
Integer c = a + b;
System.out.println("a + b = " + c);
}
}
Output:
boxed: 42 (тип Integer)
unboxed: 42 (тип int)
a + b = 30
Пример 2. Коллекции работают только с обёртками
import java.util.ArrayList;
import java.util.List;
public class BoxingCollection {
public static void main(String[] args) {
// List<int> писать нельзя — только List<Integer>
List<Integer> pins = new ArrayList<>();
// Кладём int — компилятор автоматически бокcит
pins.add(3);
pins.add(7);
pins.add(9);
int sum = 0;
for (int p : pins) { // здесь unboxing на каждом шаге
sum += p;
}
System.out.println("Пины: " + pins);
System.out.println("Сумма: " + sum);
}
}
Output:
Пины: [3, 7, 9]
Сумма: 19
Пример 3. Integer cache (-128..127)
public class BoxingCache {
public static void main(String[] args) {
// В диапазоне -128..127 Integer.valueOf возвращает кэшированный объект
Integer a = 100;
Integer b = 100;
System.out.println("100 == 100: " + (a == b)); // true (один объект)
Integer c = 200;
Integer d = 200;
System.out.println("200 == 200: " + (c == d)); // false (разные объекты)
// Правильный способ — всегда equals
System.out.println("equals(200): " + c.equals(d)); // true
}
}
Output:
100 == 100: true
200 == 200: false
equals(200): true
Пример 4. NullPointerException при unboxing null
public class BoxingNpe {
public static void main(String[] args) {
Integer x = null;
try {
int y = x; // unboxing null -> NPE
System.out.println(y);
} catch (NullPointerException e) {
System.out.println("Поймали NPE при разворачивании null");
}
// Часто незаметная ловушка — арифметика
Integer count = null;
try {
int z = count + 1; // тоже NPE
} catch (NullPointerException e) {
System.out.println("Снова NPE при count + 1");
}
}
}
Output:
Поймали NPE при разворачивании null
Снова NPE при count + 1
Пример 5. Производительность — лишний боксинг в цикле
public class BoxingPerf {
public static void main(String[] args) {
// ПЛОХО: Long в цикле — миллион ненужных боксингов
long t1 = System.nanoTime();
Long sumBoxed = 0L;
for (long i = 0; i < 1_000_000L; i++) {
sumBoxed += i; // unbox, +, box на каждой итерации
}
long t2 = System.nanoTime();
// ХОРОШО: примитив
long t3 = System.nanoTime();
long sumPrim = 0L;
for (long i = 0; i < 1_000_000L; i++) {
sumPrim += i;
}
long t4 = System.nanoTime();
System.out.println("Сумма (Long): " + sumBoxed);
System.out.println("Сумма (long): " + sumPrim);
System.out.println("Long ~ " + (t2 - t1) / 1_000_000 + " мс");
System.out.println("long ~ " + (t4 - t3) / 1_000_000 + " мс");
}
}
Output (значения времени зависят от железа):
Сумма (Long): 499999500000
Сумма (long): 499999500000
Long ~ 15 мс
long ~ 2 мс
Пример 6. Все обёртки и их примитивы
public class BoxingAll {
public static void main(String[] args) {
Byte b = (byte) 10;
Short s = (short) 20;
Integer i = 30;
Long l = 40L;
Float f = 1.5f;
Double d = 2.5;
Character c = 'A';
Boolean z = true;
System.out.println("Byte: " + b + " (" + Byte.SIZE + " бит)");
System.out.println("Short: " + s + " (" + Short.SIZE + " бит)");
System.out.println("Integer: " + i + " (" + Integer.SIZE + " бит)");
System.out.println("Long: " + l + " (" + Long.SIZE + " бит)");
System.out.println("Float: " + f);
System.out.println("Double: " + d);
System.out.println("Character: " + c + " (код " + (int) c + ")");
System.out.println("Boolean: " + z);
}
}
Output:
Byte: 10 (8 бит)
Short: 20 (16 бит)
Integer: 30 (32 бит)
Long: 40 (64 бит)
Float: 1.5
Double: 2.5
Character: A (код 65)
Boolean: true
Подводные камни
Предупреждение
Сравнение через ``==`` для обёрток сравнивает ссылки, а не значения. Используйте
equals(). Кэш Integer (-128..127) делает ошибку незаметной на малых числах и явной на больших.Unboxing null → NPE. Это самая частая «скрытая» ошибка:
Integer x = map.get("k"); int y = x;падает, если ключа нет.Производительность: в горячих циклах автобоксинг создаёт миллионы лишних объектов и грузит GC. Для числовых стримов есть специализации:
IntStream,LongStream,DoubleStream.Generics не дружат с примитивами: нельзя
List<int>, толькоList<Integer>. Каждое добавление и чтение — это box/unbox.Тернарный оператор
cond ? intValue : nullможет неожиданно бросить NPE из-за единого типа выражения.
Совет
Правило: примитивы — для расчётов и циклов, обёртки — для коллекций, generic-API и значений, которые могут быть «отсутствующими» (вместе с Optional).
См. также
Примечание
Материал основан на официальной документации Oracle Java SE (docs.oracle.com/en/java/javase), распространяемой под лицензией Oracle Free Documentation License. Тексты и примеры написаны заново для AlashEd Wiki.