Can someone explain to me how this is possible? I ...
# stdlib
z
Can someone explain to me how this is possible? I mean, it makes sense, A and B are different types, but theyre still value classes.
Copy code
sealed interface BranchIdentifier {
    val key: String
}

@JvmInline
value class A(override val key: String) : BranchIdentifier

@JvmInline
value class B(override val key: String) : BranchIdentifier

fun main() {
    val collection = mutableSetOf<BranchIdentifier>()
    collection.add(A("5"))
    collection.add(B("5"))
    println(collection.size) // Prints 2
    println(collection.contains(A("5"))) // true
    println(collection.contains(B("5"))) // true
    println(collection.remove(A("5"))) // true
    println(collection.contains(B("5"))) // true
    println(collection.size) // 1
}
j
I'm not sure I understand. What did you expect to see?
z
I was expecting it to work! But it still made me curious to know just how the jvm distinguishes between them at runtime!
Its obviously not String == String
j
My question is what does "work" mean to you? The behaviour marked in comments is exactly what should happen IMO, so I don't know what you were expecting to be different here.
Value classes behave like regular classes in every way, except that referential equality doesn't exist for them. Two instances of the same value class with the same "value" are the same in every way and are indistinguishable. But other value classes are different, that's the whole point (to have different types and not confuse what would otherwise be potentially equal values).
If you're curious about the implementation, note that value classes are not always inlined. If an abstract parent is used, or generics, a boxed instance is used at runtime instead. So if you're using a
List<A>
it will contain boxed values at runtime. If you're using a
val branchId: BranchIdentifier = A("5")
it should also use a boxed value IIRC
e
yeah, the
value
part of value classes disappears as soon as you box them - same as primitives
z
Thank you, and my appologies, I realize my question was quite unclear. Its the boxing part that I was curious about, good to know that it is in fact boxed for that scenario, which makes the whole thing obsolete!
e
Off topic - To remove possible confusion - make SetString, if you really wan’t/care about string value
d
The underlying reason is that any references to your value class "instances" that aren't hard-coded using your value class boxes the value. In your case, the mutable set stores generic types so passing a value class to any method of that set will box the value. Probably more surprising is that using sealed interfaces for value classes defeats the purpose of using value classes. That's because variables of that sealed interface box the value so that you can later distinguish which type of instance you have.
👀 1
z
Wait, so magic isnt real? I have a ton of sealed interfaces where some of them are.. value classes 😮
d
Value classes with interfaces are usually faster and more efficient as regular classes. Another red flag is if you often store these values in generic collections. With value classes, you could end up with multiple wrapper boxes instances for the same underlying value. On the other hand, Immutable Arrays are a great use-case of value classes: https://github.com/daniel-rusu/pods4k/tree/main/immutable-arrays
z
Ahh, its you! 😃
Your knowledge about these things have inspired me a ton, ever since you being on the fragmented podcast.
Migration to immutable arrays is on my todo list, and I especially love the asList (etc) functions so that I can just bake them in without too big of a refactor.
d
Wow, thank you 🙏