[Java] HashSet과 HashMap에서 equals 오버라이딩시 hashCode도 재정의 해주어야 하는 이유
Web/Java

[Java] HashSet과 HashMap에서 equals 오버라이딩시 hashCode도 재정의 해주어야 하는 이유

자바의 최상위 클래스인 Object 클래스에는 equals과 hashCode 메서드가 있습니다.

 

Object 클래스에 대한 설명은 아래링크의 글에서 맨 아랫부분을 참고하세염

2021.07.13 - [Web/Java] - [Java] 상속

 

[Java] 상속

학습할 것 (필수) 자바 상속의 특징 super 키워드 메소드 오버라이딩 다이나믹 메소드 디스패치 (Dynamic Method Dispatch) 추상 클래스 final 키워드 Object 클래스 자바 상속의 특징 상속 상속이란, 기존의

ksabs.tistory.com

 

Object의 equals는 객체의 주소를 비교하여 같은 객체인지 확인합니다.

 

 

 

그렇다면, 만약에 이름과 나이가 같다면 "같다"고 인식해줘야 하는 객체 Person이 있다고 생각해봅시다.

package javastudy.ch6;

class Person {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


public class EqualsAndHashCode {
    public static void main(String[] args) {
        Person p1 = new Person("동호", 25);
        Person p2 = new Person("동호", 25);

        System.out.println("p1과 p2는 같습니까? " + p1.equals(p2));

    }
}

 

 

 

Object 클래스의 equals는 객체의 주소를 비교하는 것이기 때문에 다른 인스턴스인 p1, p2는 다른객체라고 인식합니다.

 

그래서 Person을 이름과 나이만 같다면 같은 객체로 인식하게 해야한다면, equals 메서드를 오버라이딩 해주어야합니다.

 

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

        if (this.getClass() != obj.getClass()) {
            return false;
        }

        if (this == obj) {
            return true;
        }
        
        Person person = (Person) obj;

        if (this.name == null && person.name != null) {
            return false;
        }

        if (this.age == person.age && this.name.equals(person.name)) {
            return true;
        }

        return false;
    }

 

public class EqualsAndHashCode {
    public static void main(String[] args) {
        Person p1 = new Person("동호", 25);
        Person p2 = new Person("동호", 25);

        System.out.println("p1과 p2는 같습니까? " + p1.equals(p2));

    }
}

 

 

 

이제, 상황에 따라 equals를 재정의 해주어야할 필요가 있다는 것을 알았습니다.

 

 

 

하지만 이 Person 객체가 HashMap이나 HashSet의 key로 들어갈때, 같은 키로 인식할 수 있을까요?

 

public class EqualsAndHashCode {
    public static void main(String[] args) {
        Person p1 = new Person("동호", 25);
        Person p2 = new Person("동호", 25);

        HashMap<Person, Integer> map = new HashMap<>();

        map.put(p1, 1);
        map.put(p2, 2);

        System.out.println("map.size = " + map.size());

        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);

        System.out.println("set.size = " + set.size());
        
    }
}

 

우리는 p1과 p2가 같은 객체라고 인식하는 반면에, HashSet과 HashMap은 같은 객체로 인식하지 않아서 키가 둘 다 들어가는 상황이 발생했습니다.

 

 

이 이유는, HashSet과 HashMap은 HashCode까지 이용해 같은 객체인지 판별하기 때문입니다.

 

그런데 Object 클래스의 HashCode 메서드는 객체의 주소로 HashCode를 반환합니다.

 

객체의 주소가 다르기 때문에 HashCode도 달라집니다.

(사실 64bit JVM에서는 주소가 다르면 무조건 HashCode도 다르지는 않습니다. 그렇지만 HashCode가 같을 경우는 아주 드뭅니다.)

 

 

 

이러한 Side Effect를 방지하기 위해서 우리는 equals를 재정의 할때 hashCode도 재정의 해주어야 합니다.

 

 

이 경우에는, Person의 HashCode를 멤버의 이름과 나이를 이용해서 계산하도록 재정의 하면 됩니다.

    @Override
    public int hashCode() {
        final int prime = 31;
        int hashCode = 1;

        hashCode = prime * hashCode + ((name == null) ? 0 : name.hashCode());
        hashCode = prime * hashCode + age;

        return hashCode;
    }

 

이 포스팅에선 HashCode 재정의 규칙에 대해서 다루지는 않겠습니다.

 

 

 

다시 한번 같은 코드를 테스트 해봅시다.

public class EqualsAndHashCode {
    public static void main(String[] args) {
        Person p1 = new Person("동호", 25);
        Person p2 = new Person("동호", 25);

        HashMap<Person, Integer> map = new HashMap<>();

        map.put(p1, 1);
        map.put(p2, 2);

        System.out.println("map.size = " + map.size());

        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);

        System.out.println("set.size = " + set.size());

    }
}

 

이제는 HashMap과 HashSet에서 HashCode를 이용해 객체를 비교해도 같다고 인식해주는 것을 볼 수 있습니다.

 

 

 

 

 

 

간단 정리

- Object의 equals와 hashCode는 객체의 주소를 가지고 계산을 한다.
- 우리는 equals를 객체의 주소가 아닌 다른 비교로 바꾸어주어야 할 경우가 있다.
- 이때 HashSet, HashMap은 equals뿐만 아니라 hashCode로도 객체를 비교하기 때문에
	HashCode도 재정의 해주어야한다.

'Web > Java' 카테고리의 다른 글

[Java] brew로 자바 특정버전 설치하고 적용하기  (0) 2021.07.19
[Java] 패키지  (2) 2021.07.19
[Java] 상속  (1) 2021.07.13
[Java] Node와 BinaryTree 구현  (2) 2021.07.08
[Java] 클래스  (0) 2021.07.08