본문 바로가기
Languages/Java

[Java] equals(), hashCode()를 재정의 해야 하는 이유

by 젊은오리 2023. 4. 12.
728x90

객체의 동일성은 객체의 메모리 위치가 같은 지를 비교하기 때문에, 이는 자바에서 직접 제어할 수 없다. 따라서, 객체의 동일성은 equals()메서드나 hashCode()메서드와 별개의 개념이며, 앞서 말한대로 개발자가 직접 제어할 수 없다.

대신, 개발자는 equals()메서드와 hashCode()를 @Override로 재정의하여 객체의 동등성을 비교할 수 있다. 여기서 말하는 동등성은 객체 내용이 같은 지 비교하는 것을 의미한다.

equals()와 hashCode()를 재정의한 예제를 살펴보자.

public class Animal {

    private String name;
    private int age;

    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o){
        //현재 객체와 o의 참조값이 같은지 확인
        if(this == o)
            return true;

        //o가 Animal의 인스턴스인지 확인
        if(!(o instanceof Animal))
            return false;

        //값을 비교하기 위해 Animal객체로 casting
        Animal animal = (Animal) o;

        //name이 String이므로 equals()로 비교 -> NullPointerException 발생 X
        return Objects.equals(name,animal.name) && age == animal.age;
    }

    @Override
    public int hashCode(){
        //name, age필드의 해시코드를 반환
        //만약 equals()메서드가 name필드만을 기준으로 객체를 비교했다면,
        //Objecdts.hash(name)을 반환하도록 구현해야 한다.
        //동일한 name, age필드를 가진 Animal객체는 동일한 해시코드를 반환하게 된다.
        return Objects.hash(name,age);
    }

    public static void main(String[] args){
        Animal animal1 = new Animal("cat", 3);
        Animal animal2 = new Animal("cat", 5);

        Animal animal3 = new Animal("dog", 3);
        Animal animal4 = new Animal("dog", 3);

        //name만 같으면 equal한지 비교
        System.out.println(animal1.equals(animal2)); //false

        //age만 같으면 equal한지 비교
        System.out.println(animal1.equals(animal3)); //false

        //name,age 모두 같으면 equal한지 비교
        System.out.println(animal3.equals(animal4)); //true

        System.out.println(animal1.hashCode()); // 3047086
        System.out.println(animal2.hashCode()); // 3047088
        System.out.println(animal3.hashCode()); // 3089928 같다
        System.out.println(animal4.hashCode()); // 3089928 같다
    }
}

우리는 일반적으로 객체를 판별할 때, equals()메서드를 사용하면 충분하다. equals()의 반환이 true라면 같은 객체일 것이고, false라면 다른 객체일 것이다. hashCode()메서드는 객체가 같은지 여부를 판단하는데 도움을 주는 보조 역할을 한다.

 

🤔왜 메서드를 재정의해야 하는가?

우리는 이렇게 equals(), hashCode()메서드를 재정의함으로써 동등한 객체를 같은 해시 코드로 매핑하여 HashMap과 같은 Hash Table을 사용하는 자료구조에서 검색 성능을 높일 수 있다.

HashSet, HashMap과 같은 해시 기반의 자료구조에서는 객체의 해시 코드 값을 기반으로 저장, 검색을 수행한다.

아래 그림은 해싱한 데이터를 버킷에 삽입하는 과정을 보여준다. 여기서 John Smith을 검색하고자 한다면, HashMap(HashSet등 )은 내부적으로 hashCode()을 사용해서 해시 코드를 알아내고 버킷을 찾은 후, equals()메서드를 이용하여 John Smith 데이터를 찾아낸다.

따라서 equals()메서드와 hashCode()메서드를 적절히 구현한다면 같은 객체를 같은 버킷에 매핑할 수 있으며, 이로 인해 검색 속도를 높일 수 있다.

 

🤔Hash 기반의 자료구조를 사용하지 않는다면?

HashMap, HashSet과 같은 Hash 기반 자료구조를 사용하지 않는다면, equals(), hashCode()메서드를 재정의 하더라도 검색 성능은 당연히 높아지지 않는다.

하지만 우리가 원하는 바에 맞게 객체의 동등함을 표현하고 싶을 때, 예를 들어 위 예시와는 다르게 “Animal의 name만 같을 때 객체가 같다고 표현하고 싶다” 라고 한다면, 이를 아래와 같이 사용자 정의대로 구현할 수 있는 것이다.

@Override
public boolean equals(Object o){
    if(this == o)
        return true;

    if(!(o instanceof Animal))
        return false;

    Animal animal = (Animal) o;
    return Objects.equals(name,animal.name); //name의 동등함만 확인
}

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

또한 다른 개발자가 클래스 설계시에 클래스를 HashMap에 사용할 수도 있으므로, 일반적으로 equls(), hashCode()메서드를 적절하게 재정의하는 것이 좋다.

728x90

댓글