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