equals, hashCode, toString
Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 35 of 57.
Every class inherits toString(), equals() and hashCode() from Any. The inherited versions work on identity: toString() gives the class name and a hash code, and equals() is true only for the same object. Overriding toString() gives objects a readable text, used by println and string templates:
class Money(val cents: Int) {
override fun toString() = "$%d.%02d".format(cents / 100, cents % 100)
}Inside main:
val price = Money(1250)
println(price)
println("total: ${Money(99)}")Output:
$12.50
total: $0.99== calls equals(), while === checks whether two references point to the same object. To compare by value, override equals. Its parameter is Any?, so it checks the type first and then compares the relevant properties:
class Isbn(code: String) {
val digits = code.filter { it.isDigit() }
override fun equals(other: Any?) = other is Isbn && other.digits == digits
override fun hashCode() = digits.hashCode()
}Inside main:
val a = Isbn("978-0-441-17271-9")
val b = Isbn("9780441172719")
println(a == b)
println(a === b)Output:
true
falseObjects that are equal must have equal hash codes. HashSet and HashMap find entries by hashCode() first and only then call equals(), so overriding equals without hashCode makes equal objects look different in them. Always override both, computed from the same properties:
class Isbn(code: String) {
val digits = code.filter { it.isDigit() }
override fun equals(other: Any?) = other is Isbn && other.digits == digits
override fun hashCode() = digits.hashCode()
override fun toString() = "ISBN $digits"
}Inside main:
val shelf = hashSetOf(Isbn("978-0-441-17271-9"), Isbn("9780441172719"), Isbn("0-14-143951-3"))
println(shelf.size)
println(Isbn("9780441172719") in shelf)Output:
2
trueA data class generates all three from its constructor properties. Write them yourself when equality should use fewer properties or a normalized form, such as an email address that ignores case:
class User(val email: String, val name: String) {
private val key = email.trim().lowercase()
override fun equals(other: Any?) = other is User && other.key == key
override fun hashCode() = key.hashCode()
override fun toString() = "$name <$key>"
}Inside main:
val users = listOf(User("Ada@Example.com", "Ada"), User("ada@example.com ", "A. Lovelace"))
println(users.distinct())Output:
[Ada <ada@example.com>]Challenge
EasyTwo products are the same when their sku matches, whatever the other fields say. Override equals() and hashCode() in Product on sku only, and toString() so a product prints as A1 tea 450. Then complete mergeCatalog(products), which keeps the first occurrence of every product in the original order.
The supplied code reads products as sku,name,price lines and a last line with a sku to look up. It prints every kept product, removed: 2 and A1 in catalog: true, looking the sku up in a HashSet.
Your code goes in Product.kt and Functions.kt. Main.kt holds the supplied input/output code and cannot be edited.
Try it yourself
fun main() {
// Supplied input/output code: keep it as it is
val input = generateSequence(::readLine).toList()
val products = input.dropLast(1).map { line ->
val (sku, name, price) = line.split(",")
Product(sku, name, price.toInt())
}
val kept = mergeCatalog(products)
for (p in kept) println(p)
println("removed: ${products.size - kept.size}")
val probe = Product(input.last(), "?", 0)
println("${input.last()} in catalog: ${probe in kept.toHashSet()}")
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesPrimary Constructorsinit Blocks and ValidationSecondary ConstructorsRecap - Library Card4Inheritance
Open Classes and SubclassesOverriding Memberssuper and ConstructorsAbstract ClassesRecap - Staff Directory7Special Classes
Data ClassesEnum ClassesSealed Hierarchiesequals, hashCode, toStringRecap - Order Tracker2Properties and Encapsulation
Custom AccessorsVisibility ModifiersPrivate Setterslazy and lateinitRecap - Bank Account5Interfaces
Declaring InterfacesDefault MethodsMultiple InterfacesInterfaces as TypesRecap - Payment Methods8Generics
Generic FunctionsGeneric ClassesType ConstraintsVariance With in and outRecap - Priority Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Kotlin playground