728x90
반응형
equals를 재정의할 때 hashCode를 재정의하지 않으면 HashMap이나 HashSet같은 컬렉션의 원소로 클래스의 인스턴스를 사용할 때 문제가 된다.
Object 명세 규약
- equals 비교에 사용되는 정보가 바뀌지 않으면 몇 번을 호출해도 hashCode는 같은 값을 리턴한다.
어플리케이션이 재실행되면 값이 달라질 수도 있다. - equals(Object)가 두 객체를 같다고 판단하면 hashCode도 같아야 한다.
- equals가 다르다고 hashCode도 다른 값을 반환할 필요는 없다.
하지만 다른 값을 반환해야 해시테이블의 성능이 좋아진다.
@Getter
@Setter
@AllArgsConstructor
public class User {
private int id;
private int phone;
private int age;
}
public static void main(String[] args) {
HashMap<User, String> map = new HashMap<>();
map.put(getUser(), "user1");
String result = map.get(getUser());
System.out.println(result);
}
private static User getUser() {
User user = new User(1, 110, 20);
return user;
}
위의 예제에서 result는 null이 리턴된다.
hashCode를 재정의하지 않았기 때문에 논리적 동치라고 해도 다른 해시코드를 반환한다.
⛔️최악의 hashCode 절대 사용금지
@Override
public int hashCode() {
return 42;
}
같은 hashCode를 리턴해주면 괜찮을 것 같지만 모두 똑같은 해시값은 모든 객체가 해시테이블 버킷 하나에 담기게 되므로 수행시간이 느려진다. 객체가 많으면 도저히 쓸 수 없다.
좋은 해시 함수는 다른 인스턴스에 다른 해시코드를 반환한다.
좋은 hashCode 작성 요령
- int 변수 result를 선언한 후 아래 방법으로 초기화 한다.
- 해당 객체에 핵심 필드에 대해 아래 작업을 수행한다.
- 해당 필드의 해시코드를 구한다.
- 기본 타입 필드라면 Type.haspCode(f) -> Type은 기본타입의 박싱 클래스
- 참조 타입 필드면 필드의 hashCode를 재귀 호출 -> 필드 값이 null 이면 0
- 필드가 배열이라면 핵심 원소 각각을 별도 필드로 -> 모든 원소가 핵심 원소면 Arrays.hashCode
- 필드의 해시코드를 result로 갱신한다.
- result = 31 * result + c (위에서 구한 해시코드);
- 곱하는 숫자가 31인 이유는 홀수이며 소수이기 때문
- 짝수일 경우 오버플로가 발생하면 정보를 잃을 수 있음
- 해당 필드의 해시코드를 구한다.
- result 를 반환한다.
hashCode 작성시 주의할 점
- 파생 필드는 해시 코드 계산에서 제외해도 된다.
- equals 비교에 사용되지 않는 필드는 반드시 제외한다.
@Override
public int hashCode() {
int result = Integer.hashCode(id);
result = 31 * result + Integer.hashCode(phone);
result = 31 * result + Integer.hashCode(age);
return result;
}
전형적인 hashCode메서드이다.
hashCode를 구현하는 다른 방법
1️⃣ Objects클래스
@Override public int hashCode() {
return Objects.hash(id, phone, age);
}
Objects클래스는 임의의 개수만큼 객체를 받아서 hashCode를 계산해주는 hash메서드를 제공한다.
코드는 간결하지만 속도가 느리다. (오토박싱일 경우 더 느려진다.)
2️⃣ hashCode 지연 초기화
private int hashCode;
@Override
public int hashCode() {
int result = hashCode; // default=0
if( result == 0) {
result = Integer.hashCode(id);
result = 31 * result + Integer.hashCode(phone);
result = 31 * result + Integer.hashCode(age);
hashCode = result;
}
return result;
}
hash의 키로 사용 되는 객체가 아니라면 hashCode가 처음 불릴 때 계산하는 lazy initialization을 써도 좋다.
3️⃣ Lombok 사용
@EqualsAndHashCode
public class User {
private int id;
private int phone;
private int age;
// Exclude는 hashCode생성시 제외
@EqualsAndHashCode.Exclude private String userName;
}
롬복을 통해서 간편하게 hashCode를 생성할 수 있다.
hashCode 생성시 핵심필드를 제외하면 안되기 때문에 lombok을 사용하면 간편하게 hashCode를 만들 수 있다.
728x90
반응형
'책리뷰 > 이펙티브자바' 카테고리의 다른 글
[이펙티브자바] 아이템10. equals는 일반 규약을 지켜 재정의하라 (0) | 2022.03.02 |
---|---|
[이펙티브자바] 아이템9. try-finally보다는 try-with-resources를 사용하라 (0) | 2022.02.28 |
[이펙티브자바] 아이템8. finalizer와 cleaner사용을 피하라 (0) | 2022.02.24 |
[이펙티브자바] 아이템7. 다 쓴 객체 참조를 해제하라 (0) | 2022.02.23 |
[이펙티브자바] 아이템6. 불필요한 객체 생성을 피하라 (0) | 2022.02.15 |