Hashcode in immutable data classes

I would like to know if data classes that are immutable (containing only val) have String like optimized hashcode. I need said data classes to be as fast as possible as keys in a HashMap.

In other words, is it interresting to do this, or does the compiler do it for me?

data class Key(
        val bind: Bind, // This is also an immutable data type
        val argType: Type
) {
    private var _hashCode: Int = 0;

    override fun hashCode(): Int {
        if (_hashCode == 0)
            _hashCode = super.hashCode()
        return _hashCode
    }
}

No, the hash code value is not stored in the implementation generated for the data classes. If that’s needed, the value should be stored manually.

Thanks :wink:

@udalov: is the implementation that I’ve shown in my first message correct?

Will calling super.hashCode() will call the data class generated hashCode function or just Any.hashCode?

No, super.hashCode() refers to the implementation in the super class (Any here). Unfortunately, there’s no way to reuse the automatically generated implementation: as soon as you declare your own hashCode() function, the implementation is not generated anymore. So you need to implement hashing logic manually from scratch, save the result of the computation to the backing property and call it in the hashCode() function body.

Issue opened here : https://youtrack.jetbrains.com/issue/KT-12991 :slight_smile:

2 Likes

It would be nice if Kotlin just implemented this optimization, so I wouldn’t need to implement it myself for every immutable data class I create (I think everywhere I’ve used data classes they have been effectively immutable). I suppose it would need to be opt-in somehow, as suggested in the related issue.

2 Likes

Jumping in to note that this is a really big issue for the project I’m working on at the moment.

The project has a rather complex hierarchy of data classes (hundreds of them), all of which are immutable. Several of the data classes at the top of the hierarchy are stored in various HashMaps.

We’ve had to manually implement a caching version of hashCode() for many of the data classes because calculating it every time was causing severe performance problems (the app was unusable).

This makes the code quite awkward to maintain, and prone to hard-to-find bugs (forgetting to include a newly added field in a hash calculation can cause bugs that go undetected for a long time, and only pop up in the field).

Data classes are great solution aside from this (this hierarchy of nested immutable data classes greatly simplifies concurrency during document updates between the UI and the render thread in the app, and also made it trivially easy to implement lightweight infinite-level undo; not to mention being the core of the architecture for editing documents synced across different app instances via the cloud).

It would be really really nice if it were possible to annotate a data class as immutable so we get a cached hashCode implementation automatically.

3 Likes

I feel your pain. You want a way to get the hashcode calculated without doing it by hand or being stuck to the default implementation.I think a way to do this is using kotlin serialization. You can write a (simple) serializer that calculates the hashcode as “serialization” of the data. It comes with the normal serialization limitations, but for data classes it should be fine. You don’t even need to rely on reflection as serialization generates static code. As a side-effect your data classes now support other serialization as well.

This implementation using 0 as a placeholder seems weird—0 is a valid hashcode. Would make more sense to use Int? and null. And why can’t you just use lazy?

2 Likes