JavaがHashSetとHashMapの同じ要素をどのように判断するかについての研究


HashSetとHashMapはJDKで最も一般的な2つのクラスであり、HashSetは同じオブジェクトを格納できないこと、HashMapは同じキーを格納できないことを要求している.
    では、Javaランタイム環境は、HashSetで同じオブジェクト、HashMapで同じキーをどのように判断しているのでしょうか.「同じもの」が格納された後、Javaが実行されると、環境はどのように維持されますか?
    この問題を研究する前に,まずJDKによるequals(Object obj)とhashcode()の2つの方法の定義と仕様を説明する.
    Javaでは、オブジェクトクラスで定義されているため、equals(Object obj)とhashcode()の2つのメソッドがあります.
equals(Object obj)メソッドは、2つのオブジェクトが「同じ」かどうかを判断し、「同じ」場合はtrueを返し、そうでなければfalseを返します.
hashcode()メソッドはint数を返し、Objectクラスでのデフォルトの実装は「オブジェクトの内部アドレスを整数に変換して返す」です.
    次に、この2つの方法に関する2つの重要な規範があります(私は最も重要な2つを抽出しただけで、実は2つだけではありません):
仕様1:equals(Object obj)メソッドを書き換える場合はhashcode()メソッドを書き換える必要があり、equals(Object obj)メソッドで判断した結果trueの2つのオブジェクトが等しいhashcode()戻り値を備えていることを確認する.簡単に言えば、「2つのオブジェクトが同じであれば、hashcodeは等しいはずだ」ということです.ただし、これは仕様にすぎません.equals(Object obj)がtrueを返し、hashcode()が2つの等しくない値を返すようにクラスを書かなければならない場合は、コンパイルと実行は間違っていません.しかしこれでJava仕様に違反し、プログラムはBUGを埋めた.
仕様2:equals(Object obj)がfalse、すなわち2つのオブジェクトが「異なる」場合、hashcode()メソッドを呼び出して2つの異なる数を得る必要はありません.簡単に言えば、「2つのオブジェクトが異なる場合、hashcodeは同じかもしれません」ということです.
     この2つの規範に基づいて、以下の推論が得られる.
1、2つのオブジェクトequalsの場合、Javaランタイム環境はhashcode
きっと等しい.
2、もし2つのオブジェクトがequalsでなければ、彼らのhashcode
等しい可能性があります.
3、2つのオブジェクトhashcodeが等しい場合、彼らは
必ずしもequalsとは限らない.
4、2つのオブジェクトhashcodeが等しくない場合、彼らは
きっとequalsではない.
    
    これにより,Javaランタイム環境がHashSetとHastMapの2つのオブジェクトが同じか異なるかをどのように判断するかを推定できる.私の推測は、hashcodeが等しいかどうかを判断してから、equalsかどうかを判断することです.
    テストプログラムは次のとおりです.まずクラスを定義し、hashCode()とequals(Object obj)メソッドを書き換えます.

class A {

	@Override
	public boolean equals(Object obj) {
		System.out.println("  equals");
		return false;
	}

	@Override
	public int hashCode() {
		System.out.println("  hashcode");
		return 1;
	}
}

次にテストクラスを書きます.コードは次のとおりです.

public class Test {

	public static void main(String[] args) {
		Map<A,Object> map = new HashMap<A, Object>();
		map.put(new A(), new Object());
		map.put(new A(), new Object());
		
		System.out.println(map.size());
	}
	
}

     実行後の印刷結果は次のとおりです.
   
    判定ハシコード    判定ハシコード    判断equals    2
    Javaランタイム環境ではnew A()というオブジェクトのhashcode()メソッドが呼び出されることがわかります.次のようになります.
印刷された最初の行「判定hashcode」は、map.put(new A()、new Object()が初めて印刷されたものです.
次の「判定hashcode」と「判定equals」は2回目のmap.put(new A(),new Object()に印刷されます.
     では、なぜこのような印刷結果になったのでしょうか.私はこのように分析しました.
     1、初めてmap.put(new A()、new Object()の場合、Java実行時環境は、このmapに現在追加されているnew A()オブジェクトと同じキーがあるか否かを判断し、判断方法:new A()オブジェクトのhashcode()メソッドを呼び出し、mapに現在new A()オブジェクトと同じHashCodeが存在するか否かを判断する.明らかに、このmapにはまだ何もないので、この時は同じではありません.したがってこのときhashcodeが等しくなければequals(Object obj)メソッドを呼び出す必要はない.推論4を参照してください(2つのオブジェクトhashcodeが等しくない場合、彼らは
きっとequalsではない)
     2、2回目のmap.put(new A(),new Object()のときにJava実行時環境が再度判断すると、mapの中に同じhashcodeが2つ(Aクラスのhashcode()メソッドは永遠に1を返すので)あることが判明したので、equals(Object obj)メソッドを呼び出して判断する必要がある.推論3(2つのオブジェクトhashcodeが等しい場合)を参照してください.
必ずしもequalsではない)、2つのオブジェクトがequalsではないことがわかりました(equals(Object obj)メソッドを書き換えたので、falseは永遠に返されます).
     3、この時点で判断が終了し、判断結果:2回にわたって格納された対象は同じ対象ではない.そこで最後にmapの長さを印刷したときに表示される結果は:2です.
    書き換え手順は次のとおりです.
import java.util.HashMap;
import java.util.Map;


class A {

	@Override
	public boolean equals(Object obj) {
		System.out.println("  equals");
		return true;
	}

	@Override
	public int hashCode() {
		System.out.println("  hashcode");
		return 1;
	}
}


public class Test {

	public static void main(String[] args) {
		Map<A,Object> map = new HashMap<A, Object>();
		map.put(new A(), new Object());
		map.put(new A(), new Object());
		
		System.out.println(map.size());
	}
	
}

    実行後の印刷結果は次のとおりです.
  
    判定ハシコード    判定ハシコード    判断equals    1   
   
     Javaランタイム環境では2つの同じオブジェクトが格納されていると考えられるため、mapの長さは1になっているのは明らかです.原因は上記の分析方式に基づいて分析することができる.
    以上の分析はHashMapであるが,HashSetの下層自体がHashMapによって実現されているため,彼の判断原理はHashMapと同様であり,hashcodeを先に判断してequalsを判断する.
    だから:プログラムを書く時できるだけ規範に従うべきで、さもなくば知らず知らずのうちにバグを埋めました!