Javaのレコードについてメモ:equals/hashCode/toString
Java 17がリリースされたのでレコードについて軽く調べてメモっておきます。
とりあえず書きたくなったのはequals
/hashCode
/toString
についてです。
環境
$ java --version
java 17 2021-09-14 LTS
Java(TM) SE Runtime Environment (build 17+35-LTS-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-LTS-2724, mixed mode, sharing)
バイトコードを見てみる
まず、こんな感じのレコードを作ります。
public record Person(String name, int age) {
}
これをコンパイルしてからjavap
するとhashCode
メソッドは次のようにindyが使われていました。
public final int hashCode();
descriptor: ()I
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #21, 0 // InvokeDynamic #0:hashCode:(LPerson;)I
6: ireturn
LineNumberTable:
line 1: 0
equals
とtoString
も同じようにindyを使ったバイトコードになっていました。
ただ、呼び出されているメソッドのコードがどこにあるのかはわかりませんでした。
仕様を確認
8.10.3. Record Membersにequals
/hashCode
/toString
の仕様が書かれていました。
equals
はthis
と引数が同じコンポーネントを持っているかチェックします。
コンポーネントが参照型の場合はコンポーネントのequals
を呼び出します。
コンポーネントがプリミティブの場合はラッパークラスのcompare
を呼び出して、戻り値が0
の場合は同値とします。
hashCode
はすべてのコンポーネントのハッシュ値を計算します。
コンポーネントが参照型の場合はコンポーネントのhashCode
を呼び出します。
コンポーネントがプリミティブの場合はボクシングしてラッパークラスのhashCode
を呼び出します。
各コンポーネントのハッシュ値をどう合わせるのかは記載が見当たりませんでした。
toString
はレコードの型名と各コンポーネントの文字列表現を組み合わせます。
コンポーネントが参照型の場合はコンポーネントのtoString
を呼び出します。
コンポーネントがプリミティブの場合はボクシングしてラッパークラスのtoString
を呼び出します。
toString
すると、こんな感じになりました。
Person[name=緋村剣心, age=28]
簡単に実装を確認
スタックトレースを使うとどんな実装かわかるかもと思い、次のようなJavaプログラムを書いて実行しました。
public class App {
public static void main(String[] args) {
Foo foo1 = new Foo(new Bar());
Foo foo2 = new Foo(new Bar());
System.out.println("[equals]");
foo1.equals(foo2);
System.out.println("[hashCode]");
foo1.hashCode();
System.out.println("[toString]");
foo1.toString();
}
record Foo(Bar bar) {
}
static class Bar {
@Override
public int hashCode() {
new Throwable().printStackTrace(System.out);
return 0;
}
@Override
public boolean equals(Object obj) {
new Throwable().printStackTrace(System.out);
return true;
}
@Override
public String toString() {
new Throwable().printStackTrace(System.out);
return "";
}
}
}
出力は次の通りです。
[equals]
java.lang.Throwable
at App$Bar.equals(App.java:30)
at java.base/java.util.Objects.equals(Objects.java:64)
at App$Foo.equals(App.java:17)
at App.main(App.java:8)
[hashCode]
java.lang.Throwable
at App$Bar.hashCode(App.java:24)
at java.base/java.util.Objects.hashCode(Objects.java:103)
at App$Foo.hashCode(App.java:17)
at App.main(App.java:11)
[toString]
java.lang.Throwable
at App$Bar.toString(App.java:36)
at java.base/java.lang.String.valueOf(String.java:4215)
at java.base/java.util.Objects.toString(Objects.java:147)
at App$Foo.toString(App.java:17)
at App.main(App.java:14)
java.util.Objects
が使われていました。
以上です。
Discussion