이번 포스팅은 이펙티브 자바의 아이템 중 "equals를 재정의하려거든 hashcode도 재정의 하라."
에 대한 내용입니다.
개발을 할때 equals와 hashcode를 같이 재정의해야 한다는 말을 많이 들어봤을 것입니다.
그 이유에 대해서 알아 보겠습니다.
잠깐 해시 충돌에 대해서 다루어 보겠습니다.
Seperate chaining
과 Open addressing
기법을 사용합니다.
- 둘 다 시간 복잡도는 O(n)이지만, open addressing은 연속된 공간에 데이터를 저장하기 때문에 seperate chaining에 비해 캐시 효율이 좋습니다.
- 데이터가 적다면 open addressing이 효율이 좋습니다.
Open Addressing 기법은 데이터를 삭제할 때, 처리가 효율적이지 않지만, HashMap에서는 remove() 메서드가 빈번하게 호출되기 때문입니다.
HashMap에 저장된 키-값 쌍 개수가 일정 개수 이상으로 많아지면, 일반적으로 Open Addressing은 Separate Chaining보다 느립니다.
이러한 이유로, 해시 테이블 성능을 고려한다면 다른 값을 리턴하는 것이 좋습니다.
결론을 먼저 말씀 드리면, Collection을 사용할 때, 예를 들어 hashSet에 저장을 한다고 할 때, 같은 객체임에도 불구하고, 다른 2개의 객체로 인식되어 저장될 수 있습니다.
public class Person{
private String name;
private int age;
//생성자, getter등 생략..
}
아래의 코드는 당연히 false가 나옵니다. equals를 재정의하지 않았기 때문입니다.
Person p1 = new Person("name1", 10);
Person p2 = new Person("name1", 10);
p1.equals(p2) //false
equals를 재정의 해주면, p1과 p2는 같은 객체로 인식될 것입니다.
public class Person{
private String name;
private int age;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
Person p1 = new Person("name", 10);
Person p2 = new Person("name", 10);
Set<Person> personSet = new HashSet<>();
personSet.add(p1);
personSet.add(p2);
personSet.size() == 2 //true
public class Person{
private String name;
private int age;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Person p1 = new Person("name", 10);
Person p2 = new Person("name", 10);
Set<Person> personSet = new HashSet<>();
personSet.add(p1);
personSet.add(p2);
personSet.size() == 1 //true
reference