覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Kotlin - toString, equals, hashCode

Java で以下データを保持するクラスを定義する場合、以下のようになります。

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

多くの場合にこれで終わらず toStringをオーバーライドすることが多いです。
さらに、equals, hashCode メソッドをオーバーライドすることも少なくなくありません。

これらメソッドを手動で実装するのはほとんどなく、基本的にIDEによって自動的に生成する事がほとんどです。

たとえば Intellij IDEA で自動生成すると以下のようになります

import java.util.Objects;

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

IDEによって自動生成しているので手数はすくなくてすみますが、コードだけ見るとボイラープレートコードの塊でしかないです。


Kotlin で同様のコードを書くと以下のようになります(各種関数は Intellij IDEA で自動生成しました)

class Person(val name: String, val age: Int) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Person

        if (name != other.name) return false
        if (age != other.age) return false

        return true
    }

    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + age
        return result
    }

    override fun toString(): String {
        return "Person(name='$name', age=$age)"
    }
    
}


toString

toString は、そのクラスの内容の文字列表現を取得するための手段となります。
基本的にはデバッグやログ出力時などに有効な情報となります。
デフォルトでは役に立つ情報が出力されないため、toString はオーバーライドされる方が望ましいです。


equals

クラスの内部で同じデータを保持しているかの等価性を評価するための equals をオーバーライドします。

fun main() {
    val taro1 = Person("taro", 10)
    val taro2 = Person("taro", 10)

    println("taro1 == taro2 -> ${taro1 == taro2}")

    val jiro1 = Person("jiro", 10)
    val jiro2 = Person("jiro", 11)

    println("jiro1 == jiro2 -> ${jiro1 == jiro2}")
}

上記コードの実行結果は以下の通りです

taro1 == taro2 -> true
jiro1 == jiro2 -> false

Java では、 == 演算子はプリミティブ型では値の比較をしますが、参照型の場合は参照の比較をします。
そのため、Javaでは基本的にクラス同士の比較では equals メソッドを呼び出す必要があります。Javaの初学者が必ずはまる罠です。

Kotlin では、 == 演算子は内部的に equals を呼び出して値のの比較をします。そのため、対象のクラスで equals をオーバーライドすることで == によるインスタンスの比較が行えます。
あえて参照の比較を行いたい場合は、 === 演算子を使用します。


hashCode

hashCode は equals と一緒にオーバーライドされる必要があります。
例えば、HashSetに格納のされる値の比較は、まずハッシュコードを比較して、ハッシュコードが等しい場合のみ equals で比較を行います。

これらの動作を正しくするためにも hashCode のオーバーライドも必須となります。


まとめ

Javaと同等のデータを格納するクラスを宣言してみました。
実際には、ここで宣言したようなクラスはデータクラスを利用する事でさらに少ないコード量で記述する事が可能です。
それは次のエントリで。



関連エントリ