>  기사  >  Java  >  같음을 다시 작성할 때 Java에서 해시코드를 다시 작성해야 하는 이유는 무엇입니까?

같음을 다시 작성할 때 Java에서 해시코드를 다시 작성해야 하는 이유는 무엇입니까?

王林
王林앞으로
2021-01-08 10:22:332715검색

같음을 다시 작성할 때 Java에서 해시코드를 다시 작성해야 하는 이유는 무엇입니까?

먼저 결론을 말씀드리겠습니다.

equal을 다시 작성하는 데 반드시 해시코드가 필요한 것은 아니며 실제 상황에 따라 다르다는 점을 먼저 분명히 해야 합니다. 예를 들어 컨테이너를 사용하지 않는 경우에는 필요하지 않지만 HashMap과 같은 컨테이너를 사용하고 사용자 정의 개체를 Key로 사용하는 경우 다시 작성해야 합니다.

(동영상 공유 학습: java 동영상 튜토리얼)

같음 다시 작성은 인스턴스가 비즈니스 로직에서 동일한지 여부를 확인하는 것입니다. hascode를 다시 작성하는 목적은 컬렉션의 가중치를 빠르게 결정하는 것입니다.

HashCode() 및 equals() 규정:

1. 두 개체가 동일하면 해시 코드도 동일해야 합니다.
2. 두 개체가 동일하면 equals() 메서드는 두 개체 모두에 대해 true를 반환합니다. 객체는 동일한 해시코드 값을 가지며 반드시 동일하지는 않습니다
4. 요약하자면, equals() 메서드가 재정의된 경우 hashCode() 메서드도 재정의되어야 합니다
5. 객체는 고유한 값을 생성합니다. hashCode()가 재정의되지 않으면 이 클래스의 두 객체는 ​​어쨌든 동일하지 않습니다(두 객체가 동일한 데이터를 가리키는 경우에도).

다음은 다시 작성해야 함을 보여주는 예입니다.

Put할 때 커스텀 클래스를 HashMap의 키로 사용하는 경우

HashCode를 다시 작성하지 않고 Equals만 다시 작성하면 논리 오류가 발생합니다.

다음 코드를 먼저 살펴보세요

public class Test {

    static class Order {
    
        private Long orderId;

        public Order(Long orderId) {
            this.orderId = orderId;
        }

        public Long getOrderId() {
            return orderId;
        }

        public void setOrderId(Long orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj != null && !(obj instanceof Order)) {
                return false;
            }

            return Objects.equals(this.orderId, ((Order) obj).orderId);
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    '}';
        }
    }

    public static void main(String[] args) {
        Map<Order, String> map = new HashMap<>();

        Order order1 = new Order(1000000001L);
        Order order2 = new Order(1000000001L);

        map.put(order1, "");
        map.put(order2, "");

        System.out.println(map);
    }
}

출력 실행:

{Order{orderId=1000000001}=, Order{orderId=1000000001}=}

Rewrite in 코드는 equals 메소드가 대체되었고 hashCode 메소드는 대체되지 않았습니다.

equals 재작성 논리는 orderId가 동일한 한 두 개체가 동일하다는 것입니다.
실행 결과에 따르면 동일한 orderId를 가진 두 개체가 성공적으로 맵에 추가되었습니다. 논리적으로 예상되는 결과는 맵에서 단 하나의 Order여야 하기 때문에 이는 논리적 오류입니다.
해시맵의 소스코드를 살펴보겠습니다
댓글로 판단만 봐주세요

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 通过hash算出索引  通过索引取值==null的话  直接直接插入到索引位置。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> 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<K,V>)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;
}

소스코드를 보면 해시코드만 다르면 바로 배열에 삽입할 수 있다는 것을 알 수 있습니다. 그러나 정확하게는 hashCode 메서드를 재정의하지 않았기 때문에 Object의 hashCode 메서드가 호출됩니다. Object의 hashCode는 힙에 있는 객체의 주소를 이용하여 알고리즘을 통해 int형 값을 도출하는데, 이 경우 방금 생성된 두 객체의 int형 값은 달라야 하므로 두 Order가 모두 다를 수 있습니다. 정상적으로 배열에 삽입되어 논리 오류가 발생했습니다.

hashCode 메소드 재작성:

public class TestHash {

    static class Order {


        private Long orderId;

        public Order(Long orderId) {
            this.orderId = orderId;
        }

        public Long getOrderId() {
            return orderId;
        }

        public void setOrderId(Long orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj != null && !(obj instanceof Order)) {
                return false;
            }

            return Objects.equals(this.orderId, ((Order) obj).orderId);
        }

        @Override
        public int hashCode() {
        	// 这里简单重写下   实际开发根据自己需求重写即可。
            return this.orderId.intValue() >> 2;
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    &#39;}&#39;;
        }
    }

    public static void main(String[] args) {
        Map<Order, String> map = new HashMap<>();

        Order order1 = new Order(1000000001L);
        Order order2 = new Order(1000000001L);

        map.put(order1, "");
        map.put(order2, "");

        System.out.println(map);
    }
}

출력 다시 실행:

{Order{orderId=1000000001}=}

소스 코드를 간략하게 살펴보겠습니다(더 나은 이해를 위해 키 코드만 가로채었습니다): 설명을 위한 주석으로 put order2를 사용합니다.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 重写hashCode之后两个对象的orderId相同,hashCode也肯定相同。
    // 通过hash算出索引  通过索引取值  有值不进入if。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 由于重写了hashCode  旧对象的hashCode和新的肯定相等
        if (p.hash == hash &&
        // (k = p.key) == key == false 因为比较的是对象地址
        // (key != null && key.equals(k)) == true 因为重写了equals orderId相等则相等 
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 保存旧Node
            e = p;
        .......
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
            	// value覆盖旧Node的值
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
   ........
}

그래서 order2는 order1을 덮습니다. 이것이 사용자 정의 객체를 HashMap의 키로 사용할 때 같음을 재정의하는 경우 hashCode도 사용해야 하는 이유입니다.

반대로, hashCode를 다시 작성한 후 Equals를 다시 작성해야 합니까?

답은 '예'입니다. 모두 다시 작성해야 합니다!

위 코드를 다시 작성하는 논리를 예로 들어보겠습니다. 동일한 hashCode를 갖는 두 개의 객체가 있고, order1을 넣었을 때 해시도 동일하고, 얻은 인덱스도 동일하다고 가정하겠습니다. order1을 얻을 수 있고, 얻은 후 계속할 수 있습니다. 다시 작성하지 않는다고 가정하면 같음을 사용하여 비교하면 객체 주소 비교이며 결과는 false여야 하며 이때 해시 충돌이 발생하고 연결 목록이 형성됩니다. .

또한 map.get(key)에서도 hashCode를 기준으로 검색한 후 같음을 판단합니다.

왜 평등을 판단해야 하나요? hashCode를 기준으로 찾은 것은 연결리스트이므로, 같음을 기준으로 한 연결리스트에서 동일한 Key를 갖는 값을 찾아야 합니다.

사용자 정의 클래스를 키로 사용하는 시나리오는 무엇입니까?

가장 일반적인 키는 지도의 특정 좌표에 개체를 배치하는 것과 같은 좌표입니다.

public class Test {

    static class Coordinate {
        public Coordinate(int x, int y) {
            this.x = x;
            this.y = y;
        }

        private int x;
        private int y;

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }
    }

    public static void main(String[] args) {
        Map<Coordinate, String> map = new HashMap<>();
        map.put(new Coordinate(22, 99), "手机");
        map.put(new Coordinate(44, 48), "电脑");
    }
}

관련 권장 사항:

Java 입문 튜토리얼

위 내용은 같음을 다시 작성할 때 Java에서 해시코드를 다시 작성해야 하는 이유는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제