smallufo
02/14/2022, 2:26 AMimport Three.Companion.toThree
@JvmInline
value class Three private constructor(val value: Int) {
init {
require(value >= 1)
require(value <= 3)
}
companion object {
private val array by lazy {
arrayOf(Three(1), Three(2), Three(3))
}
fun Int.toThree(): Three {
return if (this in 1..3) {
array[this - 1]
} else {
val index = (this % 3).let { r -> if (r == 0) 3 else r }.let { v -> if (v < 1) v + 3 else v }
array[index - 1]
}
}
}
}
fun main() {
println(1.toThree() == 4.toThree())
println(1.toThree() === 4.toThree()) // Identity equality for arguments of types Three and Three is forbidden
}
Online playground here :
https://pl.kotl.in/uBVYwgQgO?theme=darculaephemient
02/14/2022, 2:32 AMsmallufo
02/14/2022, 2:36 AMilya.gorbunov
02/14/2022, 5:24 AMIlmir Usmanov [JB]
02/14/2022, 11:58 AMsmallufo
02/14/2022, 1:50 PMvalue class Three
, I am building a carousel-like Int , naming Three
, which restrict any Int value to 1..3
. That is Three.of(1) == Three.of(4)
, Three.of(0) == Three.of(3)
. I use a prebuild array to simulate caching , hoping they get the same reference
private val array by lazy {
arrayOf(Three(1), Three(2), Three(3))
}
But it seems it doesn't work. When comparing reference ===
, it complains Identity equality for arguments of types Three and Three is forbidden
, which is counter-intuitive .Ilmir Usmanov [JB]
02/14/2022, 1:52 PMsmallufo
02/14/2022, 1:55 PMThree
from array :
fun of(value: Int): Three {
return value.toPeriod()
}
fun Int.toThree(): Three {
return if (this in 1..3) {
array[this - 1]
} else {
val index = (this % 3).let { r -> if (r == 0) 3 else r }.let { v -> if (v < 1) v + 3 else v }
array[index - 1]
}
}
This should be the same reference . but in fact they are not same object reference.
@Test
fun testRef() {
assertSame(1.toThree() , 4.toThree())
}
The result :
org.opentest4j.AssertionFailedError: expected: Three@46fa7c39<Three(value=1)> but was: Three@1fb700ee<Three(value=1)>
Expected :Three(value=1)
Actual :Three(value=1)
Ilmir Usmanov [JB]
02/14/2022, 2:02 PMassertSame
) function
fun Int.add255(): Int {
return this + 255
}
fun main() {
assertSame(1.add255(), 1.add255())
}
also fails.
So, this behavior is intuitive, if you consider inline classes as primitives.smallufo
02/14/2022, 2:10 PMInt
has a build cache ( -128 to 127 )
But I am returning object from array (just like the Int cache) .
And I make sure they are not out of bound , there is no reason they are not referential equal.
That's what I mean counter-intuitive .
Just like this :
fun Int.add1(): Int {
return this + 1
}
@Test
fun testInt() {
assertSame(1.add1(), 1.add1()) // ok
assertSame(127.add1(), 127.add1()) // failed , but reasonable , understandable
}
ephemient
02/14/2022, 2:15 PMIlmir Usmanov [JB]
02/14/2022, 2:17 PMthat's becauseNo, this is because primitives are boxed. You are not returning an object from an array - you are returning inline class. If you need a reference - do not use referenceless (by design) inline classes. Similar to primitives, inline classes are boxed, thus they have unstable reference.has a build cacheInt
ephemient
02/14/2022, 2:18 PMfun Int.toThree() = Three(this)
will, in bytecode, only use primitivessmallufo
02/14/2022, 2:24 PMthey have unstable reference.
. my gotcha today .
to @ephemient fun Int.toThree() = Three(this)
is not enough .
I need to do bound checking, making sure not only 1.toThree() equals 4.toThree() , but also referential equal . But TIL it seems impossible.