제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



java.lang.Object 메쏘드 분석 3 - hashCode

getClass에 대한 글과 equals에 대한 글을 다 읽으셨다는 가정하에 씁니다. 안 보셨다면, 먼저 보고 오세요.

hashCode는 hash 값을 리턴합니다. hashCode 도 equals 와 마찬가지로 몰래몰래 호출되는 경우가 많습니다. 그래서 equals와 마찬가지로 꼬박꼬박 override 해주어야 합니다.

hashCode의 구현 규약도 설명하고 넘어가야죠.
1. 같은 자바 어플리케이션에서 실행된다면, equals에서 비교하는 멤버변수가 변경되지 않는다면, 같은 int 값을 리턴해야 합니다.
equals에서 쓰는 멤버 변수를 hashCode를 구현하는데도 똑같이 쓰면 됩니다. 결국 equals의 구현과 hashCode의 구현은 동떨어질 수 없습니다.
2. a.equals(b)가 true일 경우 a의 hashCode와 b의 hashCode는 같은 값을 리턴해야 합니다.
equals의 구현에 있어서 다른 객체를 인자로 받았을 때 true를 리턴이 되어야 할 경우는 hashCode를 구현하는데 있어서도 다른 객체들의 hashCode 구현 방식을 다 고려해야 합니다. (일반적으로 equals가 다른 객체에 대해서 ture를 리턴하는 것은 하위객체이거나 같은 인터페이스를 구현한 객체가 될 것입니다.) 예를 들어, 새로 만들어진 Map의 구현체인 HashMap의 equals와 hashCode의 구현은 기존에 있던 Map의 구현체인 Hashtable의 구현을 고려해서 만들어졌습니다.(했겠죠? 안 했을리가 없을 겁니다.-_-; )
3. a.equals(b)가 false일 경우 a의 hashCode와 b의 hashCode가 반드시 다른 값을 리턴할 필요는 없지만, 가능하면 다른 값을 리턴하는 게 좋습니다.
정합성 차원에서는 크게 신경쓸 필요는 없는 조항입니다.(즉, 이 조항을 안 지켰다고 해서 프로그램이 비정상적인 작동을 하진 않습니다.) 하지만, 성능상으로는 신경써야 합니다.(이 조항을 안 지키면 프로그램이 느려질 수 있습니다.) 이 조항이 사실 hashCode라는 메쏘드가 존재하는 이유입니다.
hashCode의 값을 기준으로 종류별로 구분할 수가 있습니다. 예를 들어, 수많은 요소를 들고 있는 Set을 생각해 봅시다. a라는 요소가 그 Set에 있는 지 없는 지를 판단하려면, 그 수많은 요소를 다 뒤져야 합니다. 하지만, hashCode 별로 요소들을 나눠서 저장해 놓으면, a와 hashCode가 같은 요소들만 뽑아서 a와 비교해 보면 됩니다. 비교할 대상을 확 줄일 수 있다는 것이지요. 자세한 것은 HashMap 분석을 참조하세요.

hashCode가 몰래몰래 호출되는 경우가 있다고 했습니다. 그런 경우를 살펴보도록 하죠.

HashMap이나 HashSet, Hashtable과 같은 객체들이 몰래몰래 hashCode를 호출합니다.

public class Person {
    private final String name;
    public Person(String name){
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        return name.equals(((Person)obj).name);
    }
}

public class Test {
    public static void main(String[] arg){
        Map<Person, Integer> map = new HashMap<Person, Integer>();
        map.put(new Person("test"), 20);
        map.put(new Person("test"), 20);
        System.out.println(map.size());
    }
}

위의 코드를 실행시켜보면, 2가 찍힙니다. new Person("test")라고 똑같은 객체를 만든 것처럼 보입니다. 또한 new Person("test").equals(new Person("test)) 는 true를 리턴하도록 만들었습니다.
문제가 되는 것은 hashCode()를 구현하지 않았다는 데 있습니다.
HashMap은 키값을 내부적으로 hashCode의 리턴값을 이용하여 관리합니다. put할 때 hashCode를 호출하여 기존에 들어있는 키 중에서 hashCode 값이 같은 것이 있는 지를 먼저 검사합니다. 키가 같은 것이 있다면, 그다음에 equals를 호출하여, 정말로 같은 키인지를 판단합니다. 그렇기 때문에 equals를 호출해서 true가 리턴되는 경우라도 hashCode가 다른 값을 리턴하면 안 됩니다. hashCode가 먼저 호출되기 때문이지요.

HashMap, Hashtable 등의 키로 쓰이는 객체는 Immutable로 써야 합니다. HashSet에 쓰는 객체도 마찬가집니다.
Immutable이 아닌 객체를 사용했을 때의 위험성을 보여주는 예를 보도록 하겠습니다.

public class Person {
    private String name;
    public Person(String name) {
        super();
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = PRIME * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Person other = (Person) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

public class Test {
    public static void main(String[] args) {
        Set<Person> set = new HashSet<Person>();  
        Person p1 = new Person("바뀌기전");
        set.add(p1);
        p1.setName("바꾼후");

        Person p2 = new Person("바꾼후");
        set.add(p2);

        System.out.println(set.size());
    }
}

위 코드는 2를 출력합니다. p1과 p2의 내용은 결국 같게 되는데도 불구하고 말입니다.
p1은 setName이 호출되기 전과 후가 다른 hashCode를 리턴합니다. 호출되기 전에 set에 저장되고, 저장된 후 hashCode 값이 바뀝니다. 즉, set 객체는 set에 저장되는 순간에는 처음 설정된 hashCode 값을 기억하고 있습니다. 그러나, 나중에 hashCode 값이 바뀌었을 때는 set 객체는 바뀌었다는 것을 인식하지 못합니다. 그러므로, 파란 부분의 코드가 호출될 때는 이미 set에 들어간 p1에 대해서는 신경을 쓰지 않습니다.

hashCode나 equals의 key로 Immutable을 사용할 수 없을 때는 변경 가능한 필드들은 hashCode와 equals에서 사용하면 안 됩니다. 예를들어 "저금통장"이라는 클래스가 있고, 멤버 변수로 통장번호, 통장주인, 잔액 이라는 3가지의 멤버 변수를 가진다고 할 때 잔액은 변경이 가능할 겁니다. 이 경우 잔액을 hashCode와 equals에서는 체크하는 멤버로 사용하면 안 됩니다.

by 삼실청년 | 2007/12/11 18:52 | 컴터질~ | 트랙백 | 핑백(5) | 덧글(9)

트랙백 주소 : http://iilii.egloos.com/tb/4000476
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at 건실성실착실 3실 청년! : .. at 2008/06/30 19:21

... equals()로 비교해서 true를 리턴하는 지 체크해서 기존 element를 덮어 쓸 것인지를 결정합니다.hashCode()는 java.lang.Object에 정의가 되어 있으며, 여기에 정리가 되어있습니다.3. bucketHashMap은 내부적으로 bucket을 사용합니다. bucket은 Map이 가지는 element들을 적당히 분산시켜 놓 ... more

Linked at Spaurh의 느긋한 블로그 .. at 2008/11/24 06:40

... 여기 ... more

Linked at 손hy : HashMap 이란.. at 2011/04/11 11:27

... 여기 ... more

Linked at java.lang.Object.. at 2013/03/26 17:49

... //iilii.egloos.com/3999011 equals()http://iilii.egloos.com/3999066 hashCode()http://iilii.egloos.com/4000476 clone()http://iilii.egloos.com/4022941 finalize()http://iilii.egloos.com ... more

Linked at java.lang.Object.. at 2013/03/26 17:49

... //iilii.egloos.com/3999011 equals()http://iilii.egloos.com/3999066 hashCode()http://iilii.egloos.com/4000476 clone()http://iilii.egloos.com/4022941 finalize()http://iilii.egloos.com ... more

Commented by 정팔이 at 2010/01/15 16:47
깊이 있는 설명 감사드립니다.
제 블로그에 링크 걸어놓고 공부하면서 보고싶은데 괜찮으신지요 ^^
Commented by 삼실청년 at 2010/01/17 23:05
냠.. 제가 java 세계를 떠나게 되서 당분간 업데이트가 없을 것 같군요.
네이버만 아니면 세상의 어디든 퍼가셔도 됩니다. 허락도 받지 않으셔도 됩니다.
Commented by 정팔이 at 2010/01/18 16:15
ㅇ ㅏ 아쉽네요 ㅠ , java 관련 글 읽고 많은 도움이 되었는데 ㅠ
저도 anti-nhn 에 관한 글을 읽어 보았습니다.
생각이 많아 지는군요 -
아무튼 이래저래 자주 놀러올게요 ^^
Commented by 삼실청년 at 2010/01/22 10:05
언젠가 돌아오게 되지 않을까 싶어요.^^
딴 거 하다가 자바 생각나면 가끔씩 올릴께요.
아쉽다고 까지 해주시니 고맙네요^^
Commented by 소심이 at 2010/05/07 21:49
좋은글 잘 보았습니다. 설명을 잘해놓으셔서 좋은 참고가 되었습니다.
Commented by 삼실청년 at 2010/05/17 12:27
하핫~ 고맙습니다. 좋은 하루 되세요.
Commented by 개발자 at 2013/12/11 16:29
정말 최고에요... 너무너무 도움 됩니다. 감사합니다.
Commented by 삼실청년 at 2014/01/24 00:35
막 도움이 되었으면 좋겠습니다^^
Commented by 준티 at 2018/08/29 17:01
감사합니다

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶