Menu
Coddy logo textTech

equals, hashCode, toString

Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 35 из 57.

Каждый класс наследует toString(), equals() и hashCode() от Any. Унаследованные версии работают с идентичностью: toString() возвращает имя класса и хеш-код, а equals() имеет значение true только для того же объекта. Переопределение toString() позволяет получить удобочитаемый текст объектов, который используется println и строковыми шаблонами:

class Money(val cents: Int) {
    override fun toString() = "$%d.%02d".format(cents / 100, cents % 100)
}

Внутри main:

val price = Money(1250)
println(price)
println("total: ${Money(99)}")

Вывод:

$12.50
total: $0.99

== вызывает equals(), а === проверяет, указывают ли две ссылки на один и тот же объект. Чтобы сравнивать по значению, переопределите equals. Его параметр имеет тип Any?, поэтому сначала проверяется тип, а затем сравниваются соответствующие свойства:

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()
}

Внутри main:

val a = Isbn("978-0-441-17271-9")
val b = Isbn("9780441172719")
println(a == b)
println(a === b)

Вывод:

true
false

Равные объекты должны иметь одинаковые хеш-коды. HashSet и HashMap сначала находят элементы по hashCode() и только затем вызывают equals(), поэтому переопределение equals без hashCode заставляет равные объекты выглядеть в них разными. Всегда переопределяй оба метода, вычисляя их на основе одних и тех же свойств:

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"
}

Внутри 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)

Вывод:

2
true

Класс данных генерирует все три на основе свойств своего конструктора. Определите их самостоятельно, если при проверке равенства следует использовать меньше свойств или нормализованную форму, например адрес электронной почты без учёта регистра:

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>"
}

Внутри main:

val users = listOf(User("Ada@Example.com", "Ada"), User("ada@example.com ", "A. Lovelace"))
println(users.distinct())

Вывод:

[Ada <ada@example.com>]
challenge icon

Задание

Легко

Два продукта считаются одинаковыми, если их sku совпадают, независимо от значений остальных полей. Переопределите equals() и hashCode() в Product, используя только sku, а также toString(), чтобы продукт выводился как A1 tea 450. Затем реализуйте mergeCatalog(products), которая сохраняет первое вхождение каждого продукта в исходном порядке.

Предоставленный код считывает продукты в виде строк формата sku,name,price, а последнюю строку — как sku для поиска. Он выводит каждый сохранённый продукт, removed: 2 и A1 in catalog: true, выполняя поиск sku в HashSet.

Ваш код должен находиться в Product.kt и Functions.kt. В Main.kt содержится предоставленный код ввода/вывода, и его нельзя изменять.

Попробуйте сами

fun main() {
    // Предоставленный код ввода/вывода: оставьте его как есть
    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()}")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Песочница Kotlin