본문 바로가기

IT/자바

[Effective Java] item 10 equals는 일반 규약을 지켜 재정의하라

반응형

equals 메서드를 재정의해서 사용하는 경우가 많은 아래의 경우엔 기본 equals 메소드를 사용하는 걸 추천한다.

1) 각 인스턴스가 본질적으로 고유한 경우 (thread -> 동작 클래스)

2) 인스턴스의 논리적 동치성을 검사할 일이 없는 경우

3) 상위클래스에서 재정의한 equals 가 하위 클래스에도 맞는 경우

4) 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없는 경우

 

하지만 equals 메서드를 재정의해야 된다면 아래의 규약을 따라야 한다. 

1) 반사성 : 자기자신에 대한 비교 =  항상 참

2) 대치성 : 서로 비교 대상을 바꾸더라도 = 항상 참

3) 추이성 : 서로 참이라면 연결관계에 있는 제3의 객체도 참이여야 한다.

더보기

추이성을 보장하는 로직을 작성하기 어려운데 '상속 대신 컴포지션을 사용'하는 방법을 사용할 수 있다.

public class InheritanceColorPoint {
    private final Point point;
    private final Color color;

    public InheritanceColorPoint(int x, int y, Color color) {
        point = new Point(x, y);
        this.color = Objects.requireNonNull(color);
    }

      //이 InheritanceColorPoint에서 Point 뷰를 반환한다.
    public Point asPoint() {
        return point;
    }

    public boolean equals(Object o) {
        if(!(o instanceof InheritanceColorPoint)) {
            return false;
        }

        InheritanceColorPoint cp = (InheritanceColorPoint) o;
        return cp.point.equals(point) && cp.color.equals(color);
    }
}

 

4) 일관성 : 처음 참이면 항상 참이여야 한다.

5) null 아님 : 널이 아니여야 한다.

 

만약 위 규약을 어길 경우 equals 동작이 정상적임을 보장할 수 없다.

 

equals를 구현했다면, 딱 3가지만 테스트 코드로 확인하자!!

1. 대칭적인가?

2. 추이성이 있는가?

3. 일관적인가?

 

그리고 equals를 재정의할때 hashCode도 반드시 재정의해야 된다. (아이템 11)

 

[equals 재정의 절차]

1) == 연산자를 이용하여 입력이 자기 자신의 참조인지 확인해야 한다.

 => 자신의 참조라면 true를 반환해야 한다.

2) instanceof 연산자로 입력된 변수가 올바른 타입인지 확인해야 한다. 그렇지 않다면 false를 반환한다.

 => 입력을 올바른 타입으로 형변환한다. 위에서 타입을 검사했으므로 무조건 성공하게 됩니다.

3) 입력된 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치한지 비교한다.

=> 모든 필드가 일치하면 true, 그렇지 않다면 false를 반환한다.

      float와 double을 제외한 기본 타입(primitive type)은 == 연산자로 비교하고

      float와 double은 부동 소숫점 등을 위해 Float.compare, Double.compare로 비교한다.

      참조 타입 필드의 경우는 각각의 equals 메서드로 비교한다.

public final class phoneNumber {
    private final short areaCode, prefix, lineNum;

    @Override
    public boolean equals(Object o) {
        if( o == this) {
            return true;
        }

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

        if(!(o instanceof PhoneNumber)) {
            return false;
        }

        PhoneNumber pn = (PhoneNumber)o;
        return pn.lineNum == lineNum && pn.prefix == prefix
                        && pn.areaCode == areaCode;
    }
}

[주의 사항]

중요한 부분은 Object 외에 타입을 매개변수로 받는 equals 메서드 작성은 안된다. 물론 오버라이드도 안된다. File 클래스인 경우는 심볼릭 링크(symbolic link)를 비교하여 같은 파일을 가리키는 지 확인하는 행동도 위험하다.

 

 

한가지 팁은 equals 메서드 재정의를 꼭 직접 할 필요는 없다. IDE에서 제공하는 기능을 이용하거나 AutoValue 프레임워크를 이용해도 된다. 오히려 이 방법이 재정의할 경우 발생하는 이슈를 더 줄여 줄 것이다.

반응형