Property include/exclude on Kotlin data classes
Solution 1
I've used this approach.
data class Person(val id: String, val name: String) {
override fun equals(other: Person) = EssentialData(this) == EssentialData(other)
override fun hashCode() = EssentialData(this).hashCode()
override fun toString() = EssentialData(this).toString().replaceFirst("EssentialData", "Person")
}
private data class EssentialData(val id: String) {
constructor(person: Person) : this(id = person.id)
}
Solution 2
This approach may be suitable for property exclusion:
class SkipProperty<T>(val property: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 0
}
SkipProperty.equals
simply returns true, which causes the embeded property
to be skipped in equals
of parent object.
data class Person(
val id: String,
val name: SkipProperty<String>
)
Solution 3
I also don't know "the idomatic way" in Kotlin (1.1) to do this...
I ended up overriding equals
and hashCode
:
data class Person(val id: String,
val name: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
Isn't there a "better" way?
Solution 4
Here's a somewhat creative approach:
data class IncludedArgs(val args: Array<out Any>)
fun includedArgs(vararg args: Any) = IncludedArgs(args)
abstract class Base {
abstract val included : IncludedArgs
override fun equals(other: Any?) = when {
this identityEquals other -> true
other is Base -> included == other.included
else -> false
}
override fun hashCode() = included.hashCode()
override fun toString() = included.toString()
}
class Foo(val a: String, val b : String) : Base() {
override val included = includedArgs(a)
}
fun main(args : Array<String>) {
val foo1 = Foo("a", "b")
val foo2 = Foo("a", "B")
println(foo1 == foo2) //prints "true"
println(foo1) //prints "IncludedArgs(args=[a])"
}
Solution 5
This builds on @bashor's approach and uses a private primary and a public secondary constructor. Sadly the property to be ignored for equals cannot be a val, but one can hide the setter, so the result is equivalent from an external perspective.
data class ExampleDataClass private constructor(val important: String) {
var notSoImportant: String = ""
private set
constructor(important: String, notSoImportant: String) : this(important) {
this.notSoImportant = notSoImportant
}
}
Jonathan Schneider
CEO @Moderne. Broad experience in Java and UI development. Passionate about developer experience and tooling. Formerly Netflix, Spring Team.
Updated on June 28, 2022Comments
-
Jonathan Schneider almost 2 years
Suppose I only want one or two fields to be included in the generated equals and hashCode implementations (or perhaps exclude one or more fields). For a simple class, e.g.:
data class Person(val id: String, val name: String)
Groovy has this:
@EqualsAndHashCode(includes = 'id')
Lombok has this:
@EqualsAndHashCode(of = "id")
What is the idiomatic way of doing this in Kotlin?
My approach so far
data class Person(val id: String) { // at least we can guarantee it is present at access time var name: String by Delegates.notNull() constructor(id: String, name: String): this(id) { this.name = name } }
Just feels wrong though... I don't really want
name
to be mutable, and the extra constructor definition is ugly.