Item10 - equals는 일반 규약을 지켜 재정의하라(3)

2023. 11. 21. 23:23Book/이펙티브 자바

 

 

구체 클래스의 하위 클래스에서 값을 추가할 방법은 없지만 괜찮은 우회 방법이 하나 있다.

"상속 대신 컴포지션을 사용하라"

 

Point를 상속하는 대신 Point를 ColorPoint의 private 필드로 두고, ColorPoint와 같은 위치의 일반 Point를 반환하는 뷰(view) 메서드를 public으로 추가하는 것이다.

 

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

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

    /**
     * 이 ColorPoint 뷰를 반환한다.
     */
    public Point asPoint() { return point; }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ColorPoint)) return false;
        ColorPoint that = (ColorPoint) o;
        return that.point.equals(point) && that.color.equals(color);
    }

    @Override
    public int hashCode() {
        return 31 * point.hashCode() + color.hashCode();
    }
}

 

 

    public static void main(String[] args) {
        Point p1 = new Point(1,  0);
        //Point p2 = new ColorPoint(1, 0, Color.RED); ==> 상속이 아니므로 타입이 맞지 않는다.
        Point p2 = new ColorPoint(1, 0, Color.RED).asPoint;

        System.out.println(onUnitCircle(p1));

        System.out.println(onUnitCircle(p2));
    }
    
    ==> true, true

 

이렇게 하면 당연히 같은 값을 가지는 Point 객체 두개를 비교하므로 true를 리턴한다.

 

4.일관성 : 두 객체가 같다면(불변 객체 시) 앞으로도 영원히 같아야 한다.

 

 클래스가 불변이든 가변이든 equals의 판단에 신뢰할 수 없는 자원이 끼어들게 해서는 안 된다. 이 제약을 어기면 일관성 조건을 지키기 어렵다. 

예)

    /**
     * Compares this URL for equality with another object.<p>
     *
     * If the given object is not a URL then this method immediately returns
     * {@code false}.<p>
     *
     * Two URL objects are equal if they have the same protocol, reference
     * equivalent hosts, have the same port number on the host, and the same
     * file and fragment of the file.<p>
     * 
        
     * [Two hosts are considered equivalent if both host names can be resolved
     * into the same IP addresses]; else if either host name can't be
     * resolved, the host names must be equal without regard to case; or both
     * host names equal to null.<p>
     *
     * Since hosts comparison requires name resolution, this operation is a
     * blocking operation. <p>
     *
     * Note: [The defined behavior for {@code equals} is known to
     * be inconsistent with virtual hosting in HTTP.]
     *
     * @param   obj   the URL to compare against.
     * @return  {@code true} if the objects are the same;
     *          {@code false} otherwise.
     */
    public boolean equals(Object obj) {
        if (!(obj instanceof URL))
            return false;
        URL u2 = (URL)obj;

        return handler.equals(this, u2);
    }

 

 URL 의 equals 메서드의 주석을 살펴보면 주어진 URL과 매핑된 호스트의 IP 주소를 이용해 비교하고 버츄얼 호스팅을 하는 경우에는 일관성을 보장할 수 없다고 나와있다.

 이는 equals가 일반 규약을 어기게 하고 문제를 일으키는 커다란 실수이다. 도메인의 이름만 비교 하면 충분하다.

 

5. null-아님 - equals 메서드에 null과 비교할 때 false를 나오게 하라(간단)