In my Kotlin/Java project, I have written some model classes that inherit an abstract class BaseItem:
/**
* All data model classes should implement this
*/
abstract class BaseItem {
/**
* A unique integer identifier used to represent the data item in the database.
*/
abstract val id: Int
override fun equals(other: Any?) = (other is BaseItem) && id == other.id
}
These model classes will be used to represent data from a database. In the database, there is an ID column that contains unique integer identifiers.
So when I use the model classes, it is guaranteed that the id property of each will unique.
After reading this the Java specification for hashCode():
- Whenever it is invoked on the same object more than once during an execution of an application, the
hashCodemethod must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.- If two objects are equal according to the
equals(Object)method, then calling thehashCodemethod on each of the two objects must produce the same integer result.- It is not required that if two objects are unequal according to the
equals(Object)method, then calling thehashCodemethod on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
My question is:
Is it good practice to return this unique identifier in hashCode()?
Note: I am aware that in Kotlin, we can use data classes so that the compiler automatically derives predefined members like equals(), hashCode(), toString(), etc, but abstract classes cannot be data classes. (However, I could make subclasses of BaseItem data classes - I'm not sure whether or not this would be the better option for this use case).
As your abstract BaseClass is for data classes (a.k.a. value classes) it should define equals and hashCode as abstract and force the implementing, concrete classes to implement them. e.g.:
abstract class BaseItem {
abstract val id: Int
abstract override fun equals(other: Any?): Boolean
abstract override fun hashCode(): Int
}
data class Person(override val id: Int, val name: String) : BaseItem()
data class Product(
override val id: Int,
val name: String,
val cost: BigDecimal
) : BaseItem()
Implementing these functions in the base class and not overriding them in the concrete sub-classes can lead to violations on the equals & hashCode contracts.
Here is an example of symmetry violation if you do not force sub-classes to implement equals/hashCode:
abstract class BaseItem {
abstract val id: Int
override fun equals(other: Any?) = (other is BaseItem) && id == other.id
override fun hashCode() = id
}
class Person(override val id: Int, val name: String) : BaseItem() {
override fun equals(other: Any?): Boolean {
return (other is Person) && id == other.id && name == other.name
}
override fun hashCode() = 31 * (31 + id.hashCode()) + name.hashCode()
}
class Product(
override val id: Int,
val name: String,
val cost: BigDecimal
) : BaseItem()
fun main(args: Array<String>) {
val baseItem1: BaseItem = Person(1, "Oliver")
val baseItem2: BaseItem = Product(1, "grease", BigDecimal.TEN)
println(baseItem1 == baseItem2) // false
println(baseItem2 == baseItem1) // true
}
If equals/hashCode were implemented according to their contracts then both equality checks would always return the same result (true or false, in this case it should be false as Product should also override these functions and check that other is also a Product and check each relevant property, etc.).
See "Item 8: Obey the general contract when overriding equals" and "Item 9: Always override hashCode when you override equals" in Effective Java, Second Edition by Joshua Bloch for more details on these contracts and the problems around different approaches to hierarchical value classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With