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>]Задание
ЛегкоДва продукта считаются одинаковыми, если их 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()}")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin