data class Person(val name:String, val age: Int)
データクラスを逆コンパイルしたところhashCodeのソースが作れませんでした。
$ jad Person.class Parsing Person.class... Generating Person.jad Couldn't fully decompile method hashCode
jadで逆コンパイルできなかったのでアセンブラからhashCodeの動きをトレースする試みです。
var bob = Person("Bob",33)//コンストラクタの解説は省略 println(bob.hashCode())//2075948//これを調べます
逆アセンブラ・コンパイラの出力
jadの出力
public int hashCode() { name; if(name == null) goto _L2; else goto _L1 _L1: hashCode(); goto _L3 _L2: JVM INSTR pop ; false; _L3: 31; JVM INSTR imul ; age; JVM INSTR iadd ; return; }
javapの出力
public int hashCode(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: dup 5: ifnull 14 8: invokevirtual #63 // Method java/lang/Object.hashCode:()I 11: goto 16 14: pop 15: iconst_0 16: bipush 31 18: imul 19: aload_0 20: getfield #19 // Field age:I 23: iadd 24: ireturn StackMapTable: number_of_entries = 2 frame_type = 78 /* same_locals_1_stack_item */ stack = [ class java/lang/String ] frame_type = 65 /* same_locals_1_stack_item */ stack = [ int ]
命令
dup
Java Virtual Machine - Online Reference - dup
スタックの最上位をそのままスタックに再度積む。
aloud_n
Java Virtual Machine - Online Reference - aload_<n>
ローカル変数0に入っている参照をスタックに積む。
invokevirtual
インスタンスメソッドを呼びます
Java Virtual Machine - Online Reference - invokevirtual
invokevirtualは<method-spec>で指定された記述子を調べ、メソッドが取る引数の数を決定します(これはゼロかもしれません)。これらの引数をオペランドスタックからポップします。次に、スタックからオブジェクト参照をポップします。 objectrefは、メソッドが呼び出されているオブジェクトへの参照です。 invokevirtualはobjectrefのJavaクラスを取得し、そのクラスとそのスーパークラスによって定義されたメソッドのリストを検索し、記述子が記述子であるmethodnameというメソッドを探します。
ifnull
スタックの先頭がnullであればラベルまで飛ぶ
Java Virtual Machine - Online Reference - ifnull
I_const_0
定数0をスタックに積む
Java Virtual Machine - Online Reference - iconst_<n>
bipush
1バイトの指定した数値(-128~127)をスタックに積む
Java Virtual Machine - Online Reference - bipush
imul
Java Virtual Machine - Online Reference - imul
スタックの最上位2つを使って掛け算
iadd
Java Virtual Machine - Online Reference - iadd
スタックの最上位2つを使って足し算。
アセンブラの動きをトレースして見ます
0: aload_0
stack |
---|
Persionのインスタンス参照(bob) |
1: getfield #11
stack |
---|
nameのStringオブジェクトへの参照("bob") |
4: dup
stack |
---|
nameのStringオブジェクトへの参照("bob") |
nameのStringオブジェクトへの参照("bob") |
5: ifnull 14
stack |
---|
nameのStringオブジェクトへの参照("bob") |
note:プライマリコンストラクタ宣言時にnameの形をnullableにしていないため nameがnullになることはないためそのまま8に進みます
8: invokevirtual #63
hashCode()のインスタンスメソッドを実行します 対象となるインスタンスはもちろん"bob"です
試しにKotlinで"bob".hashCode()を実行したら66965となったので そのままスタックに入れて見ます
stack |
---|
66965 |
11: goto 16
nameがnull出なかった時は14,15を飛ばすための処理。 16に飛びます。
14: pop
この命令は実行はされませんが、 nameがnullでない場合、name.hashCode実行時にstackからname(今回は"bob")の参照がpopされますが、 nullだと実行されず残ってしまうのでここでpopして消しています。
stack |
---|
空 |
15: iconst_0
0をstackに積みます
stack |
---|
0 |
16: bipush 31
31をスタックに積みます
stack |
---|
66965 |
31 |
18: imul
スタックの内容で掛け算をします
66965 * 31 = 2075915
stack |
---|
2075915 |
19: aload_0
Persionのインスタンスの参照をスタックに積みます
stack |
---|
Persionのインスタンス参照(bob) |
2075915 |
20: getfield #19
ageの値をスタックに積みます
stack |
---|
33 |
2075915 |
23: iadd
33 + 2075915 = 2075948
stack |
---|
2075948 |
24: ireturn
hashcodeの結果としてstackの最上位のもの(2075948)をreturnします。
結論
というわけで、正解は
nameのhashCodeに31をかけたものにageを足したものでした。 時間かけた割にあまり面白いものではなかった・・・ 同じメンバのクラスを作ってEclipseでhashCode()の自動生成をしたものと微妙に違っていますね。
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; }
これくらい逆コンパイルしてくれても良さそうなんだけど、できないのでしょうか?
おまけ:Kotlinで同じことをやってみる
var bob = Person("Bob",33) println("Bob".hashCode())//66965 println("Bob".hashCode() * 31)//2075915 println("Bob".hashCode() * 31 + 33)//2075948 println(bob.hashCode())//2075948