Menu
Coddy logo textTech

equals、hashCode、toString

CoddyのKotlinジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 35/57。

すべてのクラスは、Any から toString()、equals()、hashCode() を継承します。継承されたバージョンはアイデンティティに基づいて動作します。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() を呼び出します。一方、=== は 2 つの参照が同じオブジェクトを指しているかどうかを確認します。値で比較するには、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

データクラスは、コンストラクタのプロパティから3つすべてを生成します。等価性の判定で使用するプロパティを減らしたり、大文字と小文字を無視するメールアドレスのように正規化された形式を使用したりする場合は、自分で実装します。

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

チャレンジ

簡単

2つの製品は、他のフィールドの内容にかかわらず、skuが一致するとき同じものです。Productでequals()とhashCode()をskuだけに基づいてオーバーライドし、toString()によって製品がA1 tea 450のように表示されるようにしてください。次に、元の順序を保ったまま、各製品の最初の出現だけを残すmergeCatalog(products)を完成させてください。

提供されたコードは、製品をsku,name,price形式の行として読み込み、検索対象のskuを含む最後の行を読み取ります。保持されたすべての製品、removed: 2、およびHashSetでskuを検索した結果としてA1 in catalog: trueを出力します。

コードは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プレイグラウンド