Классы обертки в Java (оболочки типов) - Вопросы
Всего: 5 вопросов
1. Что такое классы-обертки (wrapper classes) в Java и зачем они нужны?
Что такое классы-обертки (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?
Что такое автоупаковка (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 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(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 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');