Классы обертки в Java (оболочки типов) - Вопросы

Всего: 5 вопросов

1. 

Что такое классы-обертки (wrapper classes) в Java и зачем они нужны?

Классы-обертки из пакета java.lang хранят одно значение примитивного типа в виде объекта. У каждого из восьми примитивов есть своя обертка: Byte, Short, Integer, Long, Float, Double, Character, Boolean. Имя совпадает с примитивом с заглавной буквы, исключения — Integer (int) и Character (char).

Обертки нужны, чтобы:

  • хранить числа в коллекциях и generics (List<Integer> — List<int> не компилируется);
  • обозначать отсутствие значения через null;
  • преобразовывать строки в числа и обратно (Integer.parseInt(), Integer.toString());
  • пользоваться константами и утилитами (Integer.MAX_VALUE, Character.isDigit(), Double.isNaN()).

Объекты оберток неизменяемы (immutable). Числовые обертки наследуют абстрактный класс Number, а Character и Boolean — нет.

2. 

Что такое автоупаковка (autoboxing) и распаковка (unboxing) в Java? Когда распаковка приводит к NullPointerException?

Автоупаковка — автоматическое преобразование примитива в объект обертки, распаковка — обратное преобразование. Начиная с Java 5 их выполняет компилятор: при присваивании, передаче аргумента, возврате из метода и в арифметических выражениях.

Integer boxed = 42;     // Integer.valueOf(42)
int primitive = boxed;  // boxed.intValue()

Распаковка — это вызов метода вроде intValue(), поэтому если ссылка равна null, возникает NullPointerException:

Integer count = null;
int n = count;                          // NPE
int age = map.get("unknown");           // NPE, если ключа нет
Integer x = flag ? map.get(key) : 0;    // NPE: 0 заставляет распаковать map.get(key)

Кроме того, каждая упаковка вне кэша создает новый объект, поэтому в циклах лучше использовать примитивы (long sum, а не Long sum).

3. 

Почему для Integer a = 127, b = 127; выражение a == b равно true, а для Integer c = 128, d = 128; выражение c == d равно false? Как правильно сравнивать обертки?

Для объектов оператор == сравнивает ссылки, а не значения. Автоупаковка компилируется в Integer.valueOf(), который для чисел от -128 до 127 возвращает заранее созданные объекты из внутреннего кэша IntegerCache. Поэтому обе переменные со значением 127 указывают на один объект, а для 128 каждый раз создается новый.

Аналогичный кэш -128..127 есть у Byte, Short, Long; у Character — 0..127; у Float и Double кэша нет. Верхнюю границу кэша Integer можно поднять опцией JVM -XX:AutoBoxCacheMax, так что полагаться на == нельзя.

Правильно сравнивать обертки через equals() или Objects.equals(a, b), для упорядочивания — compareTo() или Integer.compare(). Учитывайте тип: Integer.valueOf(1).equals(1L) возвращает false, потому что 1L упаковывается в Long. Сравнение обертки с примитивом через == работает корректно: обертка распаковывается.

4. 

Чем Integer.valueOf(String) отличается от Integer.parseInt(String)? Что произойдет, если строка не является числом?

Оба метода разбирают строку, различается тип результата: Integer.valueOf("42") возвращает объект Integer (для -128..127 — из кэша), а Integer.parseInt("42") — примитив int. Внутри valueOf(String) вызывает parseInt() и упаковывает результат.

int count = Integer.parseInt("42");      // примитив, без объекта
Integer boxed = Integer.valueOf("42");   // объект Integer
int bin = Integer.parseInt("101011", 2); // 43, с указанием основания

Если нужен int — используйте parseInt(), если объект (для коллекции или nullable-поля) — valueOf().

Для некорректной строки оба метода бросают непроверяемое NumberFormatException: например, для " 42" (пробелы не обрезаются), "4.2", "abc" и даже null. Исключение — Boolean.parseBoolean()/Boolean.valueOf(): они никогда не бросают исключение и возвращают false для любой строки, кроме "true" в любом регистре.

5. 

Почему конструкторы вида new Integer(42) считаются устаревшими и чем их заменить?

Конструкторы всех классов-оберток (new Integer(42), new Integer("42"), new Boolean("true") и т.д.) объявлены устаревшими в Java 9, а начиная с Java 16 помечены @Deprecated(forRemoval = true) (JEP 390) — их могут удалить в будущих версиях, компилятор выдает предупреждение.

Главная причина: конструктор всегда создает новый объект, а valueOf() может вернуть готовый объект из кэша, что экономит память. Кроме того, обертки рассматриваются как value-based классы, для которых идентичность объекта не должна иметь значения.

Integer i1 = 42;                        // автоупаковка
Integer i2 = Integer.valueOf("42");     // из строки
Boolean b1 = Boolean.valueOf("true");
Character c1 = Character.valueOf('c');
Страница 1 из 1