Java/Java 개념

[Java] equals,hashCode

chanyoun 2023. 4. 9. 21:46

equals,hashCode

스크린샷 2023-04-09 오후 9.44.49

hash 값을 사용하는 Collection에서 논리적으로 같은 객체인지 확인하는 과정은 위 그림과 같다. hashCode()에서 두 객체가 같다 판단한후 equals()메서드에서 두객체가 같다 판단한다면Collection은 동등객체라 판단한다.

HashSet<Point> points = new HashSet<>();
points.add(Point.of(1, 1));
points.add(Point.of(2, 2));
points.add(Point.of(3, 3));

Point pointToRemove = Point.of(1, 1);
points.remove(pointToRemove);

예를들어 위 코드에서 remove()메서드를 사용하더라도 points HashSet에 존재하는 첫번째 Point객체를 삭제할수없다. 그 이유는 우리가 Point클래스안에 equals() 와 hashCode()메서드를 재정의 하지않아 Object에 존재하는 equals,hashCode 메서드를 사용하며, Object에 존재하는 equals 메서드는 메모리주소가 동일한 객체를 같은객체로 판단한다.

하지만 points에 존재하는 Point.of(1,1)과 pointToRemove는 같은 메모리주소에할당된 객체 가아니기 때문에 위와같이 remove메서드를 통해 HashSet에서 좌표가 1,1 인 Point 객체를 삭제할수 없다.

따라서 위와같은 상황에서 우리가 원하는 결과값을 얻기위해선 equals 와 hashCode메서드를 재정의 해야한다.

class Point {
    private final int x;
    private final int y;
    ...
    @Override
    public boolean equals(Object o) {
        if (this == o) { //비교대상과 Remove할객체의 주소값이 같은가?
            return true;
        }
        if (o == null || getClass() != o.getClass()) { //Remove할 객체가 null이거나 비교대상과 Remove할 객체의 클래스가 다른가?
            return false;
        }
        Point point = (Point) o;
        return x == point.x && y == point.y;//비교대상과 Remove할 대상의 x,y 좌표값이 같은가?
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

public void hashSet() {
        HashSet<Point> points = new HashSet<>();
        points.add(Point.of(1, 1));
        points.add(Point.of(2, 2));
        points.add(Point.of(3, 3));

        Point pointToRemove = Point.of(1, 1);
        for (Iterator<Point> iterator = points.iterator(); iterator.hasNext();) {
            Point point = iterator.next();
            if (point.equals(pointToRemove)) {
                iterator.remove();
            }
        }
}

Object 클래스에 존재하는 equals 메서드와 hashCode메서드를 override해 우리가 원하는 결과를 기대할수있게된다.

❗❗만약 위와같은 equals메서드만을 재정의 하고 hashCode()메서드를 재정의 하지 않는다면 어떻게 될까?

위와같은 상황에서 equals메서드만 재정의한다 가정하면 우리가 원하는 remove 동작을 기대할수있다. 왜냐하면 우린 위 코드에서 HashCode를 거치지 않고  point.equals(pointToRemove) 를 통해 우리가 삭제하고싶은 객체와 points안에 있는 객체중 같은 객체라 판단되는것을 삭제하기때문이다. 하지만points.add()를 하는상황에서 문제가 발생한다.

 

만약 hashCode()를 재정의 하지않고 points.add(Point.of(2,2))를 하게된다면 Collection이 동등한 객체를 판단하기위해 hashCode()를 호출후 equals()를 호출한다. 이때 재정의되지않은 hashCode()는 Object에 존재하는 hashCode()를 실행하게 된다. 즉 Object에 존재하는 hashCode()는 객체의 메모리주소값을 가지고 같은값인지 판단하지만 우리가 새롭게 추가할 Point.of(2,2)는 기존에 추가된 Point.of(2,2)와 메모리 주소값이 다르기때문에 equals() 메서드로 가기전에 이미 동등한 객체가 아니라는 결론이 나, set값에 같은 x,y좌표를 가진 Point객체가 2개 저장되게 된다.

 

따라서 equals()메서드를 override할땐 hashCode()메서드도 override 해주자 (hash 값을 사용하는 Collection이 아니라면 hashCode()를 재정의 하지 않아도 문제가 생기지 않을수 있지만 프로그래밍은 혼자하는게 아니므로 되도록 equals()와 hashCode()메서드를 같이 재정의 해주자)

 

When you add an object to a hash-based data structure like a HashSet, HashMap, or Hashtable, the data structure first computes the hash code of the object by calling its hashCode() method. The data structure then uses this hash code to determine the bucket location for the object.

Reference