equals()와 hashCode()

1. 정의

equals()는 오버라이딩 게시글에서 언급한 적 있다. equals()와 hashCode()는 둘 다 java.lang.Object 클래스의 메서드이다.

public class Object {
    public boolean equals(Object obj) {
        return (this == obj);
    }

    // ...(생략)...

    @IntrinsicCandidate
    public native int hashCode();
}

equals()는 두 객체의 주소값을 비교한 결과를 boolean 값으로 리턴한다. hashCode()는 객체의 주소값을 int 값으로 리턴한다.

2. 오버라이딩

만일 두 객체의 주소값이 아닌 실제 데이터를 비교하고 싶으면 어떻게 해야 할까? equals() 메서드를 오버라이딩해서 구현해야 한다. 먼저 equals() 메서드를 오버라이딩 한 Coffee 클래스를 작성했다.

class Coffee {
    String name;

    Coffee(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Coffee coffee = (Coffee) o;
        return Objects.equals(name, coffee.name);
    }
}

그리고 Coffee 클래스의 객체를 생성하고 비교하기 위한 main() 메서드도 작성했다.

public class Ex1 {
    public static void main(String[] args) {
        Coffee coffee1 = new Coffee("아메리카노");
        Coffee coffee2 = new Coffee("아메리카노");

        if (coffee1.equals(coffee2)) {
            System.out.println("같은 커피입니다.");
            System.out.println(coffee1.hashCode());
            System.out.println(coffee2.hashCode());
        } else {
            System.out.println("다른 커피입니다.");
            System.out.println(coffee1.hashCode());
            System.out.println(coffee2.hashCode());
        }
    }
}

Ex1의 실행 결과는 어떻게 될까? 이름을 비교하도록 equals() 메서드를 오버라이딩 했으니 true를 반환할 것이다.

# Ex1 실행결과
같은 커피입니다.  
290658609  
1577213552  

예상대로 coffee1과 coffee2는 필드 값이 같고 주소값이 다른 객체임을 알 수 있었다. 그런데 과연 필드 값만 같으면 두 객체는 논리적으로 동등할까? 헷갈린다면 아래 예제와 함께 생각해보자.

class Ex2 {
    public static void main(String[] args) {
        Coffee coffee1 = new Coffee("아메리카노");
        Coffee coffee2 = new Coffee("아메리카노");

        List<Coffee> cafe1 = new ArrayList<Coffee>();
        cafe1.add(coffee1);
        cafe1.add(coffee2);

        System.out.println(cafe1.size());
        System.out.println(cafe1.get(0).hashCode());
        System.out.println(cafe1.get(1).hashCode());

        Set<Coffee> cafe2 = new HashSet<Coffee>();
        cafe2.add(coffee1);
        cafe2.add(coffee2);

        System.out.println(cafe2.size());
        for (Coffee c : cafe2) {
            System.out.println(c.hashCode());
        }
    }
}

Ex2의 실행 결과는 어떻게 될까?

# Ex2 실행결과(첫 번째)
2
290658609
1577213552
2
290658609
1577213552

중복을 허용하는 List와 중복을 허용하지 않는 Set 둘 다 size로 2를 출력했다. 다시 말해서, 두 객체는 논리적으로 다른 것으로 취급되었다. Collection 객체에서 논리적으로 동등하다는 건 **equals()**의 결과값과 **hashCode()**의 결과값이 동일함을 말한다. 논리적으로 동등한 비교를 위해 Coffee 클래스에 hashCode()를 오버라이딩 해보자.

class Coffee {
    String name;

    Coffee(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Coffee coffee = (Coffee) o;
        return Objects.equals(name, coffee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

Ex2의 실행 결과는 어떻게 될까?

# Ex2 실행결과(두 번째)
2
861563071
861563071
1
861563071

중복을 허용하는 List는 size로 2를 출력하지만, Set은 1을 출력했다. 또한 List의 hashCode도 동일한 값을 출력한다.

3. 주의 사항

equals()를 오버라이딩 하기 위해 참고할 주의 사항이 몇 가지 있다. 대부분은 IDE에서 자동으로 생성할테니 아래 내용은 참고하자.

  • equals()에 대한 일반 규약(참조 변수 x, y, z는 null이 아님)

    • 재귀적(reflexive): x.equals(x)는 true

    • 대칭형(symmetric): x.equals(y)가 true이면 y.equals(x)도 true

    • 전이성(transitive): x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 true

    • 일관성(consistant): 객체의 수정이 없는 경우 x.equals(y)를 여러 번 호출해도 결과값이 항상 일치

    • x.equls(null)은 false

  • hashCode()와 일관성 유지: equals()가 true이면 hashCode()도 true

  • 상속, 불변성, 성능을 고려한 구현

  • null 검사

Last updated