Упаковка и равенство
Примитивы против упакованных чисел, nullable-типы, кэш Integer и ссылочное равенство.
4 вопросов
JuniorТеорияИногдаКогда Kotlin упаковывает Int, и чем Int? отличается от Int?
Когда Kotlin упаковывает Int, и чем Int? отличается от Int?
Non-nullable Int компилируется в примитив JVM int — по значению, без аллокации. Nullable Int? должен уметь хранить null, поэтому компилируется в упакованный объект Integer. Использование Int там, где нужен объект (дженерики, коллекции), тоже его упаковывает.
Типичные ошибки
- ✗Думать, что
Intвсегда объект, ведь Kotlin прячет примитивы - ✗Считать, что
Int?иIntимеют одно runtime-представление - ✗Забывать, что помещение
Intв дженерик или коллекцию упаковывает его
Уточняющие вопросы
- →Почему сравнение двух упакованных
Int?через===иногда удивляет разработчиков? - →Какой оператор сравнения использовать для сравнения значений
Int?по содержимому?
JuniorТеорияИногдаЧем == отличается от === в Kotlin и что сравнивает каждый из них?
Чем == отличается от === в Kotlin и что сравнивает каждый из них?
== — структурное: компилируется в null-безопасный вызов equals, поэтому a == b означает a?.equals(b) ?: (b === null). === — ссылочное: спрашивает, один ли это и тот же объект. Переопределение equals меняет ответ ==; на === не влияет ничто.
Типичные ошибки
- ✗Сопоставлять
==со ссылочным==из Java вместо null-безопасного вызоваequals - ✗Думать, что
==бросает исключение, когда левый операнд равенnull - ✗Ожидать, что переопределение
equalsизменит ответ===
Уточняющие вопросы
- →Что сравнивает
===, когда оба операнда — non-nullableInt? - →Почему переопределение
equalsобязано идти в паре с согласованнымhashCode?
MiddleПроизводительностьИногдаЧего стоит @JvmInline value class в рантайме и когда он всё же упаковывается?
Чего стоит @JvmInline value class в рантайме и когда он всё же упаковывается?
value class оборачивает одно значение, и компилятор обычно стирает обёртку: функции принимают базовый тип, поэтому типобезопасность не стоит аллокации. Упаковка происходит всюду, где значение обязано быть объектом — как аргумент дженерика, внутри коллекции, при nullable-типе или там, где ожидается супертип.
Типичные ошибки
- ✗Верить, что
value classне упаковывается нигде - ✗Забывать, что nullable
value classвынуждает обёртку существовать - ✗Считать, что коллекция из них хранит базовое значение без упаковки
Уточняющие вопросы
- →Как компилятор искажает имя функции, принимающей параметр типа
value class? - →Почему реализация интерфейса у
value classвозвращает упаковку?
MiddleДебаггингРедкоПочему nullableValue === other печатает true для 1, но false для 1000?
Почему nullableValue === other печатает true для 1, но false для 1000?
Вывод — true / true / true / false. value — примитив int, поэтому === сравнивает по значению; nullableValue — упакованный Integer, и === сравнивает ссылки. JVM кэширует Integer для -128..127: 1 берёт кэшированный экземпляр, 1000 упаковывается заново.
Типичные ошибки
- ✗Думать, что
===сравнивает упакованныйIntegerпо значению, а не по ссылке - ✗Забывать, что кэш
Integerв JVM охватывает лишь-128..127 - ✗Считать, что
value(примитивint) иnullableValue(Int?) ведут себя одинаково
Уточняющие вопросы
- →Какой оператор сравнит эти значения по содержимому независимо от кэша?
- →Зачем JVM вообще кэширует маленькие значения
Integer?