Kotlin has the very useful feature of data classes, which have value semantics, that is, the compiler will automatically generate equals
and hashCode
based on the values of the fields. For example, you can represent an abstract syntax tree with declarations like
data class Plus(val a: Term, val b: Term) : Term()
But when it comes time to convert the abstract syntax tree to SSA form, identity suddenly starts mattering; this occurrence of a+b
must not be confused with that occurrence.
What's ideally needed is data classes that can be treated with value semantics in one context and reference semantics in another.
Part of this can be accomplished by using the ==
versus ===
operators for value equality versus identity comparison. The other half of the picture is collections, sets and maps, which now need to sometimes hash their keys by identity rather than contents.
Is there a way to override the default treatment of keys in sets and maps, to say this time, use identity rather than contents?
The way that I see it isn't value semantics vs reference semantics, it is about identity.
A data class represents a value. A value is immutable and doesn't have an identity which means that two values are the same if and only if they have the same content. That's why the compiler can automatically generate equals and hashcode.
If you want to model an entity which has identity, that is, a stable logical entity associated with a series of different values over time, you should use a regular class and define what its identity is. It could be an uuid or a memory address.
It is also possible to model an entity using a data class. You can define it like that:
data class Plus(val id: Long, val a: Term, val b: Term) : Term()
Or more like @marstran suggestion
data class Entity(val id: Long, val value: Any)
@marstran and @gidds solutions are practical and do the job, but I think you should be careful to not make your code confusing. There is even this warning in the IdentityHashMap javadoc: This class is not a general-purpose Map implementation! While this class implements the Map interface, it intentionally violates Map's general contract, which mandates the use of the equals method when comparing objects. This class is designed for use only in the rare cases wherein reference-equality semantics are required.