Blog

Java 의 equlas 과 hashCode, 동등성과 동일성

May 30, 2014

Java 의 equlas 과 hashCode, 동등성과 동일성

equals() 와 hashcode()

  • equals 는 두 객체의 내용이 같은지, 동등성(equality) 를 비교하는 연산자
  • hashCode 는 두 객체가 같은 객체인지, 동일성(identity) 를 비교하는 연산자

    다음과 같은 클래스가 있을때 equals() 를 이용한 두 객체의 동등성 비교는 올바른 결과가 나오지 않는다.

public class Person {
    private int id;
    private String name;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}


Person p1 = new Person();
p1.setId(1);
p1.setName("Sam");

Person p2 = new Person();
p2.setId(1);
p2.setName("Sam");

System.out.println(p1.equals(p2));

// false

따라서 eqauls() 를 오버라이딩 해 올바른 결과를 돌려줄 수 있도록 해야한다.

@Override
public boolean equals(Object obj) {

        if (obj == null) {
            return false;
        }

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

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

        Person that = (Person) obj;

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

        if (this.id == that.id && this.name.equals(that.name)) {
            return true;
        }

        return false;
    }

하지만 HashMap 이나 HashSet, HashTable 과 같은 객체들을 사용하는 경우, 객체의 의미상의 동등성 비교를 위해 hashCode() 를 호출한다.


Map<Person, Integer> map = new HashMap<Person, Integer>();
map.put(p1, 1);
map.put(p2, 1);
System.out.println(map.size());

// 2

의미상으로 p1, p2 는 같은 객체이며 equals() 까지 오버라이딩 했으나, HashMaphashCode() 를 이용해 두 객체가 동등한지 비교하므로 기대했던 대로 작동하지 않는다. hashCode() 를 오버라이딩 해야함

@Override
public int hashCode() {

    final int prime = 31;
    int result = 1;

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

    return result;
}

그러나 여전히 문제가 되는 부분이 있다. 다음의 코드를 보자.

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

Person p1 = new Person();
p1.setId(1);
p1.setName("Kally");

Person p2 = new Person();
p2.setId(1);
p2.setName("Sam");

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

p1.setName("Sam");

System.out.println(map.size());

// 2

Mapput() 하는 순간에 들어오는 오브젝트의 hashCode() 값을 기억하고 있으므로, name 이 변경된 뒤의 hashCode() 값을 인식하지 못한다.

그러므로 equals() 나 hashCode() 에서 비교하는 멤버로 immutable을 사용할 수 없다면, mutable 한 멤버는 비교의 대상으로 사용해서는 안됌.

3.1 equals() 와 관련된 규약

  1. Reflexive : Object must be equal to itself.
  2. Symmetric : if a.equals(b) is true then b.equals(a) must be true.
  3. Transitive : if a.equals(b) is true and b.equals(c) is true then c.equals(a) must be true.
  4. Consistent : multiple invocation of equals() method must result same value until any of properties are modified. So if two objects are equals in Java they will remain equals until any of there property is modified.
  5. Null comparison : comparing any object to null must be false and should not result in NullPointerException. For example a.equals(null) must be false, passing unknown object, which could be null, to equals in Java is is actually a Java coding best practice to avoid NullPointerException in Java.

3.2 hashCode() 와 관련된 규약

  1. equals() 로 비교시 두개의 오브젝트가 같다면, hashCode() 값도 같아야 한다.
  2. equals() 로 비교시 false 라면, hashCode() 값은 다를수도, 같을수도 있다. 그러나 성능을 위해서는 hashCode() 값이 다른것이 낫다. 그래야 해싱 알고리즘으로 Set 에 해당 오브젝트가 존재하는지 아닌지 빠르게 검색할 수 있다.
  3. hashCode() 값이 같다고 해서, eqauls()true 를 리턴하는 것은 아니다. 해싱 알고리즘 자체의 문제로, 같은 해시값이 나올 수 있다.

3.3 References

  1. http://www.learn-about-linux.com/2014/05/overriding-equals-and-hashcode-method.html
  2. http://iilii.egloos.com/4000476