java.lang.Objectのequals()とhashCode()の実装について調べました。
Object.equals()
JDK 8 での実装は下記の通り、参照値を比較しているだけです。
public boolean equals(Object obj) {
return (this == obj);
}
src/share/classes/java/lang/Object.java#l149
参照値とはすなわちオブジェクトへのポインタなので、Object.equals()は同一のインスタンスであるときだけtrueを返します。
The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.
参照値(単に参照とも)はオブジェクトへのポインタまたはnullです。nullはオブジェクトを参照しない特殊な参照値です。
Object.hashCode()
hashCode()はJVMの実装に依存します。
public native int hashCode();
share/classes/java/lang/Object.java#l100
下記の記事でとても詳しく説明されています。
How does the default hashCode() work?
結局のところOpenJDKのJVMでの実装は
- OpenJDK 8, OpenJDK 9
- スレッドごとに持つ値を使ってXorshiftで生成した乱数
- OpenJDK 7, OpenJDK 6
-
Park & Miller (Lehmer)の乱数発生器(os::random)で生成した乱数
線形合同法の一種らしいのですが数学弱すぎてわかりません(><)
-
Park & Miller (Lehmer)の乱数発生器(os::random)で生成した乱数
とのことでした。 XorShiftは、排他的論理和とビットシフトのみで(高速に)疑似乱数を生成できるアルゴリズムです。
hash codeは一度生成されるとObject Headerと呼ばれるインスタンスごとに持つ領域に記録されます。
二回目以降のhashCode()の呼び出し時には、Object Headerに記録されたhash codeを返します。
OpenJDK8でhash codeを生成するコードは次の通りです。
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
src/share/vm/runtime/synchronizer.cpp
スレッドのメンバ変数_hashStateX
と_hashStateW
を使ってハッシュコードを生成します。
xor-shiftのアルゴリズムは理解できていないのですが、_hashStateX, _hashStateY, _hashStateWをアルファベット順で一つ前の名前を持つ変数に値を移し、_hashStateWを生成したハッシュコードにすることで、次の呼び出し時にもユニークな値が生成できるようにしています。
_hashStateはスレッド生成時に次のように初期化されます。
_hashStateX = os::random() ;
_hashStateY = 842502087 ;
_hashStateZ = 0x8767 ; // (int)(3579807591LL & 0xffff) ;
_hashStateW = 273326509 ;
src/share/vm/runtime/thread.cpp
まとめ
Object.equals()は参照値の比較。
Object.hashCode()の値は各インスタンスでの初回呼び出し時に生成される乱数で、2回目以降の呼び出しは生成済みの乱数を返す。