Объекты и примитивы в Java — две абсолютно разные группы типов. Виртуальная машина работает с ними по-разному. Аналогично, массивы примитивов и массивы объектов — разные типы. Generics работают только со ссылочными типами. Это значит, что можно создать, например, List<String>, List<Object>, List<byte[]>, List<int[]>, List<List<byte[]>>, но нельзя — List<int> или List<long>, т. к. int и long — примитивные типы.
Так как иногда таки возникает необходимость создать список, например, целых чисел, можно создать класс, который позволит представить целое число как объект.
public class Int {
private final int value;
public Int(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
К величайшей радости, этот костыль уже написан за нас. В основном пакете, java.lang, уже есть классы Boolean, Byte, Short, Character, Integer, Float, Long, Double. Эти классы называют объектными обёртками (boxed primitives). Они предоставляют тот же функционал, что и примитивные типы, только делают это как объекты — посредством классов и методов.
Это даёт возможность создать, например, List<Integer>.
Для преобразования значения примитивного типа есть статические методы valueOf, например, Integer.valueOf(4) вернёт объект типа Integer. Для обратного преобразования есть методы intValue(), longValue() и т. п..
Integer objTen = Integer.valueOf(10);
int iTen = objTen.intValue();
Autoboxing
Компилятор может вставить вызовы valueOf и *Value автоматически, это называется autoboxing и autounboxing соответственно.
Integer objTen = 10;
int iTen = objTen;
Кэш объектов
Так как объекты класса Integer неизменяемы (immutable), их можно кешировать и переиспользовать. Но кэш всего диапазона int'ов, [-2 147 483 648, 2 147 483 647], занимал бы очень много места, поэтому Integer'ы кешируются только в диапазоне [-128, 127]. То есть Integer.valueOf(127) каждый раз возвращает один и тот же объект, а Integer.valueOf(128) каждый раз выделяет новый.
С этой особенностью связан известный паззлер, т. е. код, поведение которого сбивает с толку:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); // true
System.out.ptintln(c == d); // false
При этом, конечно же, c.equals(d) == true.
Пример использования
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
System.out.println(ints); // [1, 2]
При этом можно использовать все преимущества списков — методы contains, indexOf и прочие.
Используйте обёртки только при необходимости
Обёртки (boxed primitives) занимают в памяти больше места, чем примитивы. Так как объектные обёртки иммутабельны, любая арифметическая операция выделяет новый объект с новым значением. Например, в этом коде вычисления проводятся с примитивами, как и должно быть:
long sum = 0;
for (long n : numbers) {
sum += n;
}
System.out.println(sum);
Но если в первой строке (без причины) использовать объектную обёртку,
Long sum = 0;
for (long n /* unboxing */ : numbers) {
sum += n; // boxing — здесь выделяется новый объект
// эквивалентно sum = Long.valueOf(sum.longValue() + n)
}
System.out.println(sum);
то этот код отработает в несколько раз медленнее и выделит много новых объектов, создавая лишнюю работу для сборщика мусора.
Не используйте конструкторы обёрток
Правильный способ получить boxed primitive — это метод valueOf. Есть также неправильный — явный вызов конструктора. По ошибке конструктор публичный, хотя должен быть приватным. Integer.valueOf(0) при каждом вызове будет возвращать один и тот же объект из кэша, а new Integer(0) каждый раз будет создавать новый.