tonykang22 / study

0 stars 0 forks source link

[Effective Java] 아이템 10. equals는 일반 규약을 지켜 재정의하라. #50

Open tonykang22 opened 2 years ago

tonykang22 commented 2 years ago

아이템 10. equals는 일반 규약을 지켜 재정의하라

핵심 정리 : equals를 재정의 하지 않는 것이 최선


핵심 정리: equals 규약


핵심 정리: equals 구현 방법

== 연산자를 사용해 자기 자신의 참조인지 확인한다.


핵심 정리: 주의 사항


예시 코드

1. 대칭성

아래는 잘못된 equals 재정의의 예시이다. CaseInsensitiveString은 equals에서 String을 알고 있지만, String은 CaseInsensitiveString을 알지 못하기 때문에 벌어지는 문제이다. (양방향이 아닌 단방향으로 작동하기 때문이다.)

public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s) {
        this.s = Objects.requireNonNull(s);
    }

    @Override public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
        if (o instanceof String)
            return s.equalsIgnoreCase((String) o);
        return false;
    }

    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String polish = "polish";

        System.out.println(cis.equals(polish)); // true
        System.out.println(polish.equals(cis)); // false

        List<CaseInsensitiveString> list = new ArrayList<>();
        list.add(cis);

        System.out.println(list.contains(polish)); // false
    }
}


올바르게 사용하려면 equals를 다음과 같이 수정해야한다. (아래의 코드로 main()을 실행하면 콘솔에 false, false, false 를 확인할 수 있으며 대칭성이 지켜지는 것을 확인할 수 있다.)

    @Override public boolean equals(Object o) {
        return o instanceof CaseInsensitiveString &&
                ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
    }


2. 상속 관계에서 잘못된 equals 재정의

좌표를 의미하며 좌표가 같을 때 동일하다 판단하도록 equals를 재정의한 Point 클래스. ColorPoint 클래스는 Point를 상속받았으며, (편의상 뒤에 숫자로 구분) equals1()equals2()는 잘못된 경우이다.

public class Point {

    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

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

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

        Point p = (Point) o;
        return p.x == x && p.y == y;
    }
}
public class ColorPoint extends Point {
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }

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

//    @Override
    public boolean equals2(Object o) {
        if (!(o instanceof Point))
            return false;

        if (!(o instanceof ColorPoint))
            return o.equals(this);

        return super.equals(o) && ((ColorPoint) o).color == color;
    }

    public static void main(String[] args) {
        Point p = new Point(1, 2);
        ColorPoint cp = new ColorPoint(1, 2, Color.RED);
        System.out.println(p.equals(cp) + " " + cp.equals(p)); // true false

        ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
        Point p2 = new Point(1, 2);
        ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
        System.out.printf("%s %s %s%n",
                          p1.equals(p2), p2.equals(p3), p1.equals(p3));
    }
}


3. 상속 관계에서 잘못된 equals 재정의 2

그렇다면 아래와 같이 Point의 equals를 재정의한다면 괜찮지 않을까? (CounterPoint는 Point를 상속받았으며 equals를 재정의하지 않은 클래스이다.)

public class Point {

    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override public boolean equals(Object o) {
        if (o == null || o.getClass() != getClass())
            return false;
        Point p = (Point) o;
        return p.x == x && p.y == y;
    }
}
public class CounterPointTest {

    private static final Set<Point> unitCircle = Set.of(
            new Point( 1,  0), new Point( 0,  1),
            new Point(-1,  0), new Point( 0, -1));

    public static boolean onUnitCircle(Point p) {
        return unitCircle.contains(p);
    }

    public static void main(String[] args) {
        Point p1 = new Point(1,  0);
        Point p2 = new CounterPoint(1, 0);

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

        System.out.println(onUnitCircle(p2));
    }
}


4. Composition을 통해 equals 규약 지키기

자기 자신의 타입인지 확인 후, 가진 값들이 각각 equal한지만 검사하면 된다. (Enum의 특성상, enum의 equals는 기본적으로 객체의 동일성만 확인한다.) asPoint()를 통해Point 특성만 외부에 노출시킬 수도 있다.



완벽 공략


완벽 공략 24. Value 기반의 클래스

클래스처럼 생겼지만 int 처럼 동작하는 클래스


완벽 공략 25. StackOverflowError

로컬 변수와 객체가 저장되는 공간의 이름은?


완벽 공략 26. 리스코프 치환 원칙

객체 지향 5대 원칙 SOLID 중에 하나.