Javanese Online

Объектные обёртки. Autoboxing

Объекты и примитивы в 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) каждый раз будет создавать новый.

Комментарии к уроку

Сообщить об ошибке

Javanese.Online в GitHub

Чаты и каналы в Telegram

RSS-лента