The Kotlin Docs contains the following information.
All nullable references to a are actually the same object because of the memory optimization that JVM applies to Integers between -128 and 127. It doesn't apply to the b references, so they are different objects.
I wrote the main function and test code to check the above. At this time, each result was different.
I test this using kotest.
class Ch7Test: StringSpec({
"nullable Int between -128 and 127 should be caching" {
val boxedA: Int? = 100
val anotherBoxedA: Int? = 100
assertSoftly {
(boxedA === anotherBoxedA) shouldBe true
}
}
})
The test result was failed.
expected:<true> but was:<false>
Expected :true
Actual :false
But when I test in main function, the result was different from the test results.
fun main() {
val boxedA: Int? = 100
val anotherBoxedA: Int? = 100
println("debug: ${boxedA === anotherBoxedA}") // output was "debug: true"
}
Why is it the same logic, but the results are different?
What's actually happening is a bit obscured by the DSL syntax, but essentially when you do "foo bar bar" { ... }
in a StringSpec
, you are calling String.invoke
declared in StringSpecRootScope
.
operator fun String.invoke(test: suspend StringSpecScope.() -> Unit)
The important part here is that the lambda you pass is actually a suspend
lambda. In other words, the boxing and tests happen in a coroutine.
And apparently, boxing in coroutines is different. Instead of calling the Java boxing methods like Integer.valueOf
, one of the methods from this file is called. For Int
s, it is:
internal fun boxInt(primitive: Int): java.lang.Integer = java.lang.Integer(primitive)
As you can see, this straight up creates a new Integer
object (new Integer(primitive)
in Java). If it had used Integer.valueOf
, then the integer cache would have been used.
Try decompiling a file like
suspend fun main() {
val x: Int? = 100
}
and see which method is called for the boxing.
According to the commit that added this file,
These methods are very thin wrappers around primitive wrapper classes constructors.
They are used by coroutines code which returns primitives and this way HotSpot is able to throw the allocations away completely.
#KT-26591 Fixed
So this is actually an optimisation specifically for the HotSpot JVM.
That said, I'm not familiar with the JVM internals to explain exactly how explicitly allocating an Integer
actually makes the JVM optimise away the allocations.