Programming/JAVA

[Effective Java] 공통 메서드 정리 - equals() & hashCode()

코딩의성지 2023. 2. 1. 21:44

객체라면 가지고 있는 공통의 메서드에 대한 이해와 활용법을 정리해두려고 한다. 오늘은 equals() 와 hashCode() 메서드에 대해 정리해두도록 하겠다. 

 

1. equals

 

equals는 말그대로 객체가 같은지를 비교하는 메서드이다.  일반적으로 객체가 생성될 때 고유한 해쉬코드가 생성되게 된다. 우리는 이 해쉬코드를 hashCode 라는 공통 메서드를 호출해 가져올 수 있다. 일반적으로 정의된 equals 메서드는 내부에서 이 해쉬코드를 비교한다. 하지만 실무에서는 객체의 해시코드만 비교해서는 안될 경우가 종종 있어 equals를 오버라이딩 해서 사용한다. 

 

equals를 오버라이딩 해서 사용하기 위해서는 아래 5가지 조건을 만족해야만한다.

1. 반사성 (reflexivity)

e.equals(x) => true

2.  대칭성 (symmetry)

x.equals(y) => true then y.equals(x) => true

3. 추이성 (transitivity)

x.equals(y) => true , y.equals(z) => true then x.equals(z) => true

4. 일관성 (consistency)

x.equals(y) 를 여러번 호출해도 항상 결과는 같아야한다.

5. null 아님

만약 x가 null 이 아니면, x.equals(null) => false

 

위의 규칙을 만족시키기 위해 Equals의 전형적인 오버라이딩 패턴은 아래와 같다.

1. == 을 통해 input이 자기 자신의 참조인지 검사

2. instanceof 나 getClass 를 통해 input 타입이 명확한지 검사 , not null 체크

3. 2를 통해 검사된 객체를 올바른 타입으로 형변환

4. 핵심필드가 모두 일치하는지 검사

아래 코드는 Intellij 의 기능을 이용해 오버라이딩한 equals 메서드 인데, 위의 규칙이 만족된다.

public class Member {
    private String name;
    private String email;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Member member = (Member) o;
        return Objects.equals(name, member.name) && Objects.equals(email, member.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

구현된 equals를 보면 완전 동일한 객체가 아니라도, 내부의 필드 값이 일치하면 true를 리턴하도록 구현되어 있다.

 

equals를 오버라이딩 해서 사용하는 것이 좋긴하나, 오버라이딩 하지 않아야할 때가 있다.

첫번째는 각 인스턴스가 본질적으로 고유할 때이다. 즉 클래스가 값이 아닌 개체를 표현할때이다.

두번째는 인스턴스가 논리적으로 같음을 증명할 필요가 없어 사용하지 않을 것임이 명확할 때 이다.

세번째는 상위 클래스의 재정의 된 메서드가 하위 클래스 에서도 들어 맞을 때이다.

네번째는 private  클래스로 메서드가 사용자체가 없는 경우이다.

 

아래 코드를 보자.

public class Player {
    private String name;
    private int overall;
}
public class Team {
    private List<Player> players;
}

위의 클래스는 선수 정보를 의미하는 Player 와 선수에 대한 리스트를 가진 Team 클래스가 있다. 여기서 우리가 팀의 능력치를 평균낸다거나 선수 숫자를 카운팅 한다고 가정했을 때, 만약 같은 이름의 같은 능력치가 있는 선수가 있다면 (극단적인 경우이긴 하지만...) 잘못 구현된 equals 메서드가 완전히 다른 결과를 만들어 내버릴 여지가 있다. 이런 상황은 항상 조심할 필요가 있다. 단순하게 생각해서 논리적 동치성을 검사할 일이 없으면 그냥 기존 상위클래스 (Object)에 구현되어 있는 equals를 쓰도록 하자.

 

2. hashCode

이펙티브 자바에 따르면 equals메서드를 재정의하려면 hashCode 메서드도 재정의할 것을 권장하고 있다. (그냥 필수로 한다고 생각하자)

hashCode 메서드를 제대로 알기 위해서는 객체간 비교 방법의 차이에 대해 좀 알아둘 필요가 있다.

비교방법 내용  
== 값 비교를 위해 사용
기본형일 때는 값을 비교하고,
참조형일때는 주소값을 비교한다.
 
equals() 같은 객체인지를 비교하기 위한 용도로 사용, 기본적으로 == 의 기능과 동일하나
객체간 비교를 위해 오버라이딩이 필요하다
 
hashCode() 논리적으로 같은 객체라면 같은 hashCode를반환하는 것을 이용하여 비교한다.  

 

위에서 자동으로 재정의된 hashCode를 사용하면 아래와 같을 것인데,

@Override
public int hashCode() {
    return Objects.hash(name, overall);
}

이렇게 사용하면 속도적인 측면에서 성능이 떨어진다.

속도가 너무 느려지면 지연 초기화 전략이나 캐싱을 통해 문제를 해결할수 있는데, 이때 핵심 필드를 누락해서는 안되는것을 기억하자.

 

실무에서는 이 hashCode 를 효과적으로 구현하기 위해 Lombok 을 활용하기도 한다.

@EqualsAndHashCode
public class Player {
    private transient int tramsoemtVar = 10;
    private Strng name;
    private int overall;
    private String[] tags;
    @EqualsAndHashCode.Exclude private int id;
}

만약에 여러분이 Lombok의 @Data 어노테이션을 사용하고 있으면 

@EqualsAndHashCode 뿐만아니라 @Getter, @Setter, @RequiredArgsConstructor, @ToString 이 포함되어 있다.

 

실무에서 생각없이 @Data를 쓰는경우가 많은데, 이러한 부분이 포함되어 있음을 유의해서 사용하도록 하자.

반응형