Java Setオブジェクトの再読み込み

8497 ワード

Set集合には重複データがない特性が知られているが,要素を対象とした場合も同様に効果的であるかどうか.ちょっと見てもいいです.例を挙げます.
SetTest.java:
class VO {
    private String name;
    private String addr;

    public VO(String name, String addr) {
        this.name = name;
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "name: " + name + " addr:" + addr;
    }
}

 @Test
 public void testSet() {

     Set vos = new HashSet<>();

     VO vo = new VO("wahaha", "sh");

     VO vo1 = new VO("wahaha", "bj");

     VO vo2 = new VO("wahaha", "sh");

     vos.add(vo);
     vos.add(vo1);
     vos.add(vo2);

     for (VO item : vos) {
         System.out.println(item.toString());
     }
 }

結果:
name: wahaha addr:sh
name: wahaha addr:bj
name: wahaha addr:sh

各フィールドの値が同じオブジェクトに対して、再操作は行われていないことがわかります.なぜか、JDK 1.8のHashSetのデータ構造を見てみましょう.
HashSet.java:
オブジェクトのインスタンス化:
/**
 * Constructs a new, empty set; the backing HashMap instance has
 * default initial capacity (16) and load factor (0.75).
 */
 public HashSet() {
    map = new HashMap<>();
 }

new HashSet()操作は実際にはnew HashMap<>()であり,底層はHashMapで実現されていることがわかる.
HashSet.addメソッド:
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

HashMap.addメソッド:
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

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;
}

上の太字フィールドから分かるように、挿入したkeyが存在するか否かを判断し、2点1 hash値が同じか否かを判断する.②対応する値が同じかどうかは,前者はhashCode()メソッド,後者はequal()メソッドである.次に、hashCodeとequalの計算における基本的なデータ型とカスタムクラス型の違いを探り、次のコードを見てみましょう.
class VO {
    private String name;
    private String addr;

    public VO(String name, String addr) {
        this.name = name;
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "name: " + name + " addr:" + addr;
    }
}

@Test
public void testSet() {
    
   Set vos = new HashSet<>();

   VO vo = new VO("wahaha", "sh");
   VO vo1 = new VO("wahaha", "bj");
   VO vo2 = new VO("wahaha", "sh");

   Integer a = 2;
   Integer b = 2;

   String str1 = new String("abc");
   String str2 = new String("abc");

   System.out.println(a.equals(b));
   System.out.println(str1.equals(str2));
   System.out.println(vo.equals(vo2));
   
   System.out.println(a.hashCode() == b.hashCode());
   System.out.println(str1.hashCode() == str2.hashCode());
   System.out.println(vo.hashCode() == vo2.hashCode());

   vos.add(vo);
   vos.add(vo1);
   vos.add(vo2);

   for (VO item : vos) {
       System.out.println(item.toString());
   }
}

結果:
true
true
false
true
true
false
name: wahaha addr:sh
name: wahaha addr:sh
name: wahaha addr:bj

JAva.lang.Integer.equals():2つのオブジェクトに対応する値が一致するとtrueが返されます.
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

JAva.lang.String.equals():2つの文字列に対応する値が一致するとtrueが返されます.
public boolean equals(Object anObject) {
    if (this == anObject) {//     ,      
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {//       
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;//anObject  String  ,    false
}

JAva.lang.Object.equals():2つのオブジェクトの参照が一致しているかどうか、すなわち2つのオブジェクトが同じかどうか.
public boolean equals(Object obj) {
    return (this == obj);
}

Java.lang.Object.equals()の場合、2つのnewから出てくるオブジェクトは一致しないに違いありません.HashMapデータ構造では同じオブジェクトと判定されません(値は同じですが).次にhashCodeのソースコードを見てみましょう.
java.lang.Integer.hashCode():
@Override
public int hashCode() {
    return Integer.hashCode(value);
}

public static int hashCode(int value) {
    return value;
}

java.lang.String.hashCode():
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

java.lang.Object.hashCode():
public native int hashCode();

JDK 8のデフォルトhashCodeの計算方法は,現在のスレッドに関係する1つの乱数+3つの決定値により,Marsaglia's xorshift schema乱数アルゴリズムを用いて得られた1つの乱数である.
したがって,IntegerとStringも具体的なvalue値に基づいてhashCodeを計算していることが分かるが,2つの参照が異なるにもかかわらず値が同じオブジェクトは待ちたいが,Objectは異なる.
理由が分かったら、解決方法は簡単です.それはVOクラスを再ロードするequalsとhashCodeの方法です.コードは以下の通りです.
SetTest.java:
class VO {
    private String name;
    private String addr;

    public VO(String name, String addr) {
        this.name = name;
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "name: " + name + " addr:" + addr;
    }

    /**
     *        User,   hashcode,              
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (this == obj)
            return true;
        if (obj instanceof VO) {
            VO vo = (VO) obj;

            //                true
            if (vo.name.equals(this.name) && vo.addr.equals(this.addr))
                return true;
        }
        return false;
    }

    /**
     *   hashcode   ,   hashCode              
     */
    @Override
    public int hashCode() {
        return name.hashCode() * addr.hashCode();
    }
} // class

@Test
public void testSet() {
    
   Set vos = new HashSet<>();

   VO vo = new VO("wahaha", "sh");
   VO vo1 = new VO("wahaha", "bj");
   VO vo2 = new VO("wahaha", "sh");

   vos.add(vo);
   vos.add(vo1);
   vos.add(vo2);

   for (VO item : vos) {
       System.out.println(item.toString());
   }

}

結果:
name: wahaha addr:sh
name: wahaha addr:bj

アリババJava開発マニュアルの集合処理では、以下のルールに強制的に従う必要があります.
1)equalsを書き換える限りhashCodeを書き直さなければならない.
2)Setは重複しないオブジェクトを格納しているのでhashCodeとequalsで判断するので,Setが格納しているオブジェクトはこの2つのメソッドを書き換える必要がある.
3)カスタムオブジェクトをMapのキーとする場合はhashCodeとequalsを書き換える必要があります.
正例:StringはhashCodeとequalsメソッドを書き換えているので、Stringオブジェクトをkeyとして非常に楽しく使用することができます.
以上です.
Author:記憶の独秀
Email:[email protected]出典を明記:https://blog.csdn.net/lavorange/article/details/80420087