Примитивы и обёртки
Примитивные типы и их обёртки, autoboxing, кеш Integer, массивы и почему Java не является чисто объектным языком.
5 вопросов
JuniorТеорияОчень частоЧто такое классы-обёртки, и что делают автоупаковка и распаковка?
Что такое классы-обёртки, и что делают автоупаковка и распаковка?
Класс-обёртка вроде Integer, Double или Boolean упаковывает примитив в настоящий объект, чтобы его можно было хранить в обобщениях и коллекциях, которые держат только объекты. Автоупаковка преобразует примитив в его обёртку автоматически (например, int в Integer), а распаковка преобразует обёртку обратно в примитив, так что можно смешивать их без явных преобразований.
Типичные ошибки
- ✗Думать, что примитивы можно хранить в коллекциях напрямую, без объекта-обёртки
- ✗Путать направления: автоупаковка — примитив в обёртку, распаковка — обёртка в примитив
- ✗Полагать, что автоупаковка бесплатна, игнорируя выделение объекта во время выполнения
Уточняющие вопросы
- →Что произойдёт при распаковке Integer, который равен null?
- →Почему == может неожиданно сравнить два Integer, тогда как equals сравнивает их значения?
JuniorТеорияЧастоЧем массив отличается от ArrayList в Java?
Чем массив отличается от ArrayList в Java?
Массив — конструкция языка фиксированной длины, доступная через поле length. Он может хранить примитивы напрямую и ковариантен, поэтому неверная запись выявляется лишь во время выполнения как ArrayStoreException. ArrayList — библиотечный класс, который оборачивает массив и растёт по надобности; у него есть size() и add(), он хранит только объекты, поэтому примитивы упаковываются, а благодаря обобщениям он инвариантен, и неверный тип элемента отвергается на компиляции.
Типичные ошибки
- ✗Ожидать, что массив вырастет, хотя его длина фиксируется в момент создания
- ✗Путать поле
lengthмассива с методомsize()уArrayList - ✗Считать, что
ArrayListможет хранить примитивы без упаковки их в объекты-обёртки
Уточняющие вопросы
- →Почему переменная типа
Object[]может держатьString[]и что ломается при записи в неё? - →Что
ArrayListделает внутри, когда у него кончается ёмкость?
MiddleКодЧастоКэш Integer — предскажите три сравнения ==
Кэш Integer — предскажите три сравнения ==
r1 равно true, r2 — false, r3 — false. Автоупаковка вызывает Integer.valueOf, который кэширует объекты для -128..127, поэтому 100 возвращает один и тот же закэшированный объект (== true), а 200 вне диапазона и упаковывается в новый объект каждый раз (== false). new Integer(100) всегда выделяет память, поэтому два вызова new никогда не ==.
Типичные ошибки
- ✗Считать, что
==на боксированномIntegerвсегда сравнивает числовое значение, а не тождество ссылок - ✗Полагать, что кэш покрывает все значения, а не только
-128..127 - ✗Думать, что
new Integer(100)участвует в кэшировании, а не всегда выделяет память
Уточняющие вопросы
- →Как можно изменить верхнюю границу кэша
Integer? - →Почему конструктор
new Integer(int)объявлен устаревшим в Java 9?
SeniorТеорияЧастоКакие тонкие ошибки способны внести автоупаковка и кэш Integer?
Какие тонкие ошибки способны внести автоупаковка и кэш Integer?
Три классические. == между двумя Integer сравнивает ссылки, поэтому даёт true для 127 из кэша -128..127, но false для 128 — равенство, которое проходит все мелкие тесты и падает в проде. Распаковка null-обёртки бросает NullPointerException на строке без единого видимого разыменования, например int x = map.get(missing). А упаковка сдвигает разрешение перегрузок: list.remove(1) для List<Integer> удаляет по индексу, а list.remove(Integer.valueOf(1)) — по значению.
Типичные ошибки
- ✗Полагаться на
==между двумя обёртками, потому что для малых кэшируемых значений он случайно работает - ✗Считать, что распаковка
null-обёртки даёт0, а не бросаетNullPointerException - ✗Вызывать
list.remove(1)дляList<Integer>и ожидать удаления значения1
Уточняющие вопросы
- →Как переписать сравнение двух
Integer, чтобы оно было верным всегда? - →Почему повышение
-XX:AutoBoxCacheMaxменяет результат==на боксированных значениях?
MiddleТеорияИногдаПочему Java считается не чисто объектно-ориентированным языком?
Почему Java считается не чисто объектно-ориентированным языком?
Потому что в нём есть примитивные типы вроде int, char и boolean, которые не являются объектами: у них нет методов, они не наследуются от Object и существуют как простые значения, а не в куче. Чисто ОО-язык трактовал бы всё как объект. Java сохраняет примитивы ради производительности и связывает их с миром объектов через классы-обёртки вроде Integer.
Типичные ошибки
- ✗Приписывать нечистоту самостоятельным функциям, которых Java вне классов на деле не допускает
- ✗Утверждать, что примитивы наследуют Object и имеют методы, хотя это простые необъектные значения
- ✗Забывать, что классы-обёртки существуют именно для связи примитивов с миром объектов
Уточняющие вопросы
- →Почему Java сохранила примитивные типы вместо того, чтобы сделать каждое значение объектом?
- →Как обобщения вынуждают использовать типы-обёртки, а не примитивы?