きっかけ
Hashableを誤解していました
こちらの記事を読んで、そういえばkotlinのdata classのhashCodeはどういう実装になっているのかふと気になったので調べてみました。
hashCode3行おさらい
Effective Java 第3章 項目9 45Pの要約
- equalsで比較されるオブジェクトが変更されない限り、hashCodeメソッドは常に同じ整数結果を返さなければならない
- equalsでtrueならばhashCodeは同じ整数結果でなければならない
- equalsでfalseならばhashCodeは別々の整数結果であることが望ましい(重複可)
KotlinのData Classesのドキュメント
では以下のように記述してあります。
The compiler automatically derives the following members from all properties
declared in the primary constructor
「コンパイラは、プライマリコンストラクタで宣言されたすべてのプロパティから次のメンバーを自動的
に派生させます」
-equals()/hashCode() pair
-toString()
-componentN() functions
-copy()
どう実装しているかについては書いていないのでシンプルなdata classのバイトコードをデコンパイルしてみた
data class Sample(val id: Int, val title: String, val desc: String)
↓
↓
/*hashCodeのデコンパイル結果*/
public int hashCode() {
return (this.id * 31 + (this.title != null?this.title.hashCode():0)) * 31
+ (this.desc != null?this.desc.hashCode():0);
}
特徴
- プロパティが複数ある場合、末尾以外のプロパティに31を掛ける(参照型とプリミティブ型の順番によって掛け方が若干変化する)
- 参照型の場合はnullかどうかで、0かそのインスタンスのhashCodeを返す
#####31という数字について
Effective Javaに以下の記述があります。
Effective Java 第3章 項目9 48P
乗数31は、それが奇数の素数なので選ばれています。もし、その乗数が偶数で乗算がオーバーフローしたとしたら、2を掛けることはシフトするのと同じですので、情報が失われることになります。素数を使用することの利点は、あまり明確ではありませんが、素数が伝統的です。31の素晴らしい特性は、より良いパフォーマンスのために、乗算がシフトと減算で置き換えられることです。すなわち、31 * i == (i << 5) - i です。最新のJVMは、この種の最適化を自動的に行います。
まとめ
誰得な情報かわかりませんが、個人的な備忘録を兼ねて残しておきたいと思います。。
Effective Java定期的に読み直そうと思います。