HashSetコレクション内のadd()メソッドは、カスタムタイプの実行プロシージャを格納します.

6254 ワード

まずカスタムクラスを作成します
class Student{
	
	String id;

	public Student(String id) {// 
		this.id = id;
	}
}

mainメソッドでテスト
public static void main(String[] args) {
		HashSet  set = new HashSet<>();
		// HashSet ->HashMap ->HashMap table null
		
		set.add(new Student("1"));
		//tab=resize()->resize() table , ——>tab table , 16——> static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
		set.add(new Student("1"));
		System.out.println(set.size());
	}

このとき実行結果は2である.
初歩的な思考:StudentクラスではhashCode()メソッドは書き換えられていないため、hash(Object key)を呼び出す際に、伝達オブジェクトのアドレスを比較し、mainメソッドの2つのオブジェクト(同じオブジェクトのように見えますが、実際にはそうではありません)のアドレスが異なるため、hash値が異なります.したがって、この2つのオブジェクトはセットに格納できます.
この場合、重複idオブジェクトを追加できない場合は、カスタムクラスでhashCode()メソッドを書き換えるだけです.
	@Override
	public int hashCode() {		
		return id.hashCode();
	}

but...この時点で実行結果は2??まずputValメソッドのコードを見つけて分析します.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node[] tab; Node p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

分析を続行:mainメソッドの最初のaddメソッドを実行する場合:
ステップ1:
if((tab=table)==null|(n=tab.length)==0)判定文を実行し、最初のaddではtable配列が空であり、if文内部コードtab=resize()を実行してtabに配列空間を与え、tableはグローバル変数であり、初期値はnullであり、tabとtableは同じ配列を指す.ここでresize()は、デフォルトの長さ16を返し、tab、resize()、tabが指す同じオブジェクトにresize()を割り当てます.tableはtabにも付与.この3つは同じオブジェクトを指しています
ステップ2:
if(p=tab[i=(n-1)&hash])==null)判定を実行する.ここで、式((n-1)&hash)の結果をiに与えることで、tab[i]の値を探し、tab[i]がnullであるか否かを判断し、空であればオブジェクトを格納することができる.同じtableがグローバル変数が指すオブジェクトとして、オブジェクトを格納します.すなわち、tab/table配列にidが1のオブジェクトが格納されている
ステップ3:elseをスキップし、return null文を直接実行してnullを返す
2番目のaddメソッドを実行する場合:
ステップ1:
if((tab=table)==null|(n=tab.length)==0)判定文を実行すると、tabが指す配列にidが1のオブジェクトが既に存在するため、条件が成立せず、後の文の実行をスキップする.
ステップ2:
if(p=tab[i=(n-1)&hash])==null)判定文を実行します.このときのhashは前のステップのhash値と同じなので、iの値は変わりません.elseにジャンプして次のコードを実行します.
 if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;


p.hash==hashは、2回入力されたオブジェクトhashの値が同じかどうかを比較し、明らかにtrueである.(k=p.key)==key文では、k=p.key、すなわちkとpに格納されているのはいずれも最初のオブジェクトのアドレスであり、keyは今回の受信オブジェクトのアドレスであるため、結果は明らかにfalseであるため、key!=null && key.equals(k)文、key!=null実行キーが成立する.equals(k)は、ここで呼び出されるのは依然としてObjectクラスのequalsメソッドであり、結果は必然的にfalseである.
したがって、ここではカスタムクラスでequalsメソッドを書き換えることができます.
@Override
	public boolean equals(Object obj) {
		Student student = (Student)obj;// 
		return id.equals(student.id);
	}

equalsメソッドを書き換えると、ここではカスタムクラスのequalsメソッドが呼び出され、実際にはStringクラスのequalsメソッドが呼び出され、内容が同じかどうかを比較し、明らかにtrueである.e=p文の実行
最後:次のコードを実行します.
 if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }


eは空ではないのでif文の内容を実行し、メソッドの戻り値もnullではなく、追加に失敗しました
このときmainメソッドを再実行した結果は1であり,要求される機能は基本的に実現される.
カスタムクラスの完全なコードをここに示します.
class Student{
	
	String id;

	public Student(String id) {
		this.id = id;
	}

	@Override
	public int hashCode() {		
		return id.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		Student student = (Student)obj;// 
		return id.equals(student.id);
	}	
}

Dogクラスを作成すると
public class Dog {

	public String id;

	public Dog(String id) {
		this.id = id;
	}

	@Override
	public int hashCode() {		
		return id.hashCode();
	}
}

mainメソッド
public static void main(String[] args) {
		HashSet  set = new HashSet<>();
			
		set.add(new Dog("1"));
		set.add(new Student("1"));		
		System.out.println(set.size());
	}

実行結果:
Exception in thread "main" java.lang.ClassCastException: com.jd.Dog cannot be cast to com.jd.Student
	at com.jd.Student.equals(Test.java:35)
	at java.util.HashMap.putVal(HashMap.java:634)
	at java.util.HashMap.put(HashMap.java:611)
	at java.util.HashSet.add(HashSet.java:219)
	at com.jd.Test.main(Test.java:13)


従来、タイプ変換エラーは、前述の博文で述べたように、instanceofを使用すると簡単に解決でき、カスタムクラスのequalsメソッドのみを修正すればよい
@Override
	public boolean equals(Object obj) {
	
			if (obj instanceof Student) {
				Student student = (Student) obj;// 
				return id.equals(student.id);
			}		
			return false;
	} //main 2;

最後に、hashCode()とequals(Object obj)の実行順序はhashcode()が先に実行され、hashcodeが等しい場合equals(Object obj)が実行される