Boxing & Equality
Primitive vs boxed numbers, nullable types, the Integer cache, and reference equality.
4 questions
JuniorTheoryOccasionalWhen does Kotlin box an Int, and how does Int? differ from Int?
When does Kotlin box an Int, and how does Int? differ from Int?
A non-nullable Int compiles to the JVM primitive int — stored by value, no allocation. A nullable Int? must hold null, so it compiles to the boxed Integer. Using an Int where an object is required (generics, collections) also boxes it.
Common mistakes
- ✗Thinking
Intis always an object because Kotlin hides primitives - ✗Assuming
Int?andIntshare the same runtime representation - ✗Forgetting that putting an
Intin a generic or collection boxes it
Follow-up questions
- →Why does comparing two boxed
Int?with===sometimes surprise developers? - →Which comparison operator should you use to compare
Int?values by content?
JuniorTheoryOccasionalHow does == differ from === in Kotlin, and what does each one compare?
How does == differ from === in Kotlin, and what does each one compare?
== is structural: it compiles to a null-safe equals call, so a == b means a?.equals(b) ?: (b === null). === is referential — it asks whether both operands are the very same object. Overriding equals changes what == answers; nothing changes ===.
Common mistakes
- ✗Mapping
==to Java's reference==instead of a null-safeequalscall - ✗Thinking
==throws when the left operand isnull - ✗Expecting an
equalsoverride to change what===answers
Follow-up questions
- →What does
===compare when both operands are a non-nullableInt? - →Why must an
equalsoverride be paired with a matchinghashCode?
MiddlePerformanceOccasionalWhat does a @JvmInline value class cost at runtime, and when does it still box?
What does a @JvmInline value class cost at runtime, and when does it still box?
A value class wraps one value and the compiler usually erases the wrapper: functions take the underlying type, so the type safety costs no allocation. It boxes whenever the value must be an object — as a generic type argument, inside a collection, when made nullable, or where a supertype is expected.
Common mistakes
- ✗Believing a
value classnever boxes anywhere - ✗Forgetting that a nullable
value classforces the wrapper to exist - ✗Assuming a collection of them stores the underlying value unboxed
Follow-up questions
- →How does the compiler mangle a function that takes a
value classparameter? - →Why does implementing an interface on a
value classreintroduce boxing?
MiddleDebuggingRareWhy does nullableValue === other print true for 1 but false for 1000?
Why does nullableValue === other print true for 1 but false for 1000?
Output is true / true / true / false. value is a primitive int, so === compares by value; nullableValue is a boxed Integer, so === compares references. The JVM caches Integer for -128..127: 1 reuses a cached instance, 1000 boxes fresh.
Common mistakes
- ✗Thinking
===compares values for boxedInteger, not references - ✗Forgetting the JVM
Integercache only spans-128..127 - ✗Assuming
value(a primitiveint) andnullableValue(Int?) behave identically
Follow-up questions
- →Which operator would compare these values by content regardless of caching?
- →Why does the JVM cache small
Integervalues in the first place?