ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아이템10. equals는 일반 규약을 지켜 재정의하라
    Java/이펙티브 자바 2023. 6. 7. 11:28

    핵심정리 1 - equals가 필요없는 경우

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

    - 다음의 경우에 해당하면 equals를 재정의 할 필요가 없다.

    - 각 인스턴스가 본질적으로 고유하다.

        ㆍ싱글톤, Enum

    - 인스턴스의 '논리적 동치성'을 검사할 필요가 없다.

        ㆍ객체의 동일성을 비교 안할 때

    - 상위 클래스에서 재정의한 equals가 하위 클래스에도 적절하다.

        ㆍList, Set

    - 클래스가 private 이거나 package-private이고 equals 메서드를 호출할 일이 없다.

        ㆍpublic 클래스면 equals 가 호출안된다는 보장이 안되기 때문에.

     

    핵심정리 2 - equals 규약 - 반사성, 대칭성

    핵심 정리: equals 규약

    - 반사성 : A.equals(A) == true

    - 대칭성 : A.equals(B) == B.equals(A)

        ㆍCaseInsensitiveString

    - 추이성 : A.equals(B) && B.equals(C), A.equals(C)

        ㆍPoint, ColorPoint(inherit), CounterPointer, ColorPoint(comp)

    - 일관성 : A.equals(B) == A.equals(B)

    - null-아님 : A.equals(null) == false

     

     

    대칭성 위배!

    // 대칭성 위배!
    @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;
    }
    
    // 문제 사연(55쪽)
    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String polish = "polish";
        System.out.println(cis.equals(polish)); // true
    
        ArrayList<CaseInsensitiveString> list = new ArrayList<>();
        list.add(cis);
    
        System.out.println(list.contains(polish));  // false
    }
    // 첫 번째 equals 메서드(코드 10-2)는 대칭성을 위배한다. (57쪽)
    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

     

    핵심정리 3 - equals 규약 - 추이성

     

    추이성 위배

    - 만약 대칭성을 고려해서 코드를 작성한다면?! - 추이성 위배!

    // 코드 10-3 잘못된 코드 - 추이성 위배! (57쪽)
    @Override
    public boolean equals(Object o) {
        if(!(o instanceof Point)) {
            return false;
        }
    
        // o가 일반 Point면 색상을 무시하고 비교한다.
        if(!(o instanceof ColorPoint)) {
            return o.equals(this);
        }
    
        // o가 ColorPoint면 색상까지 비교한다.
        return super.equals(o) && ((ColorPoint) o).color == color;
    }

    - 만약 ColorPoint와 같은 레벨에 있는 class가 생긴다면 equals 실행시 stackoverflow 발생 위험!

     

    stackoverflow

    - 메서드들이 계속 호출되면서 쌓이면 나타나는 에러

     

    public static void main(String[] args) {
        // 두 번째 equals 메서드(코드 10-3)는 추이성을 위배한다. (57쪽)
        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));   // true true false
    }

     

    리스코프 치환 원칙 위배!

    - 만약 추이성 위배를 해결한다면?!

    // Point class
    // 잘못된 코드 - 리스코프 치환 원칙 위배! (59쪽)
    @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;
    }

     

    * 다음과 같이 필드를 추가한다면 추이성이나 대칭성을 위반하지 않는 equals를 추가하는 방법은 없다.

    public class ColorPoint extends Point {
       private final Color color;
    
        public ColorPoint(int x, int y, Color color) {
            super(x, y);
            this.color = color;
        }
        ...
        
    }

    - ex)

    long time = System.currentTimeMillis();
    Timestamp timestamp = new Timestamp(time);
    Date date = new Date(time);
    
    // 대칭성 위배! p60
    System.out.println(date.equals(timestamp)); // true
    System.out.println(timestamp.equals(date)); // false

     

    핵섬정리 4 - equals 규약 - 일관성, null 아님

    - 상속받은 경우에 필드를 추가하고 싶을 때, 상속받지말고, 컴포지션을 통해 필드를 추가해라.

    // 코드 10-5 equals 규약을 지키면서 값 추가하기 (60쪽)
    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의 Point 뷰를 반환한다.
         */
        public Point asPoint() {
            return point;
        }
    
        @Override
        public boolean equals(Object o) {
            if(!(o instanceof ColorPoint)) {
                return false;
            }
            ColorPoint cp = (ColorPoint) o;
            return cp.point.equals(point) && cp.color.equals(color);
        }
    
        @Override
        public int hashCode() {
            return 31 * point.hashCode() + color.hashCode();
        }
    
    }

     

    * 일관성을 문제는 복잡하게 해결해서는 안된다.

    - 최종적으로 어떤 것을 가르키냐? 로 비교한다면 복잡하게 비교하는 것이다.

    - ex) IP가 다르면 다를 수가 있다. ( 일관성 위배의 가능성이 있다. )

    // 일관성 위배 가능성 있음. p1
    URL google1 = new URL("https", "about.google", "/products/");
    URL google2 = new URL("https", "about.google", "/products/");
    System.out.println(google1.equals(google2));

     

    핵섬정리 5 - equals 구현 방법과 주의 사항

    핵심 정리 : equals 구현 방법

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

    - instanceof 연산자로 올바른 타입인지 확인한다.

    - 입력된 값을 올바른 타입으로 형변환 한다.

    - 입력 객체와 자기 자신의 대응되는 핵심 필드가 일치하는지 확인한다.

     

    - 구글의 AutoValue 또는 Lombok을 사용한다.

    - IDE의 코드 생성 기능을 사용한다.

    - 과제) 자바의 Record를 공부하시오.

     

    * 핵심적이지 않은 필드란?

    - 동기화에 사용되는 락? : 어떤 객체 하나만 가지고 있는 특수한 필드가 아니다.

    synchronized (new Object()) {
    }

     

    * float이나 double 처럼 부동소수점의 영향을 받으면 compare() 을 사용한다.

    Double.compare();

    - 부동 소수점이 아닌 int 같은 경우는 == 로 비교

     

    * null을 비교한다면?

    Objects.equals(null, null);

     

    equals 구현

    1. @AutoValue 를 사용

    - 컴파일 타임에 equals 규약에 맞춰 코드 생성

    - 단점으로 많이 침투적이고 지켜줘야하는 규약들이 있다.

     

    2. lombok 사용

    @EqualsAndHashCode
    @ToString
    public class Point {
        private final int x;
        private final int y;
    
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    - 컴파일 타임에 equals 규약에 맞춰 코드 생성

    - 사용하기 편함.

     

    3. * 자바 17버전부터는 자바의 레코드도 사용할 수 있다.

    4. 인텔리제이 기능 사용

    - 단점으로 지저분하다.

     

     

    핵심 정리 : 주의사항

    - equals를 재정의 할 때 hashCode도 반드시 재정의하자. (아이템11)

    - 너무 복잡하게 해결하지 말자.

    - Object가 아닌 타입의 매개변수를 받는 equals 메서드는 선언하지 말자.

    // @Override 을 안할 경우
    public boolean equals(Point p) {
        if(this == p) { // 반사성 : 객체의 동일성을 비교
            return true;
        }
    
        return p.x == x && p.y == y;    // 핵심적인 필드만 비교
    }
    
    public static void main(String[] args) {
        Point point = new Point(1, 2);
        List<Point> points = new ArrayList<>();
        points.add(point);
        System.out.println(points.contains(new Point(1, 2)));   // false
    }

     

    완벽공략 24 - Value 기반 클래스

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

    - 식별자가 없고 불변이다.

    - 식별자가 아니라 인스턴스가 가지고 있는 상태를 기반으로 equals, hashCode, toString을 구현한다.

    - == 오퍼레이션이 아니라 equals를 사용해서 동등성을 비교한다.

    - 동일한(equals) 객체는 상호교환 가능하다.

     

    * 자바17의 레코드

    public record Point(int x, int y) {
    }
    public class PointTest {
        public static void main(String[] args) {
            Point p1 = new Point(1,0);
            Point p2 = new Point(1,0);
            System.out.println(p1.equals(p2));  // true
            System.out.println(p1);
            
            // setter 와 getter 가 없다.
            System.out.println(p1.x());
            System.out.println(p1.y());
        }
    }

     

    완벽공략 25 - StackOverflowError 

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

    - 스택(stack)과 힙(heap)

    - 메소드 호출시, 스택에 스택 프레임이 쌓인다.

        ㆍ스택 프레임에 들어있는 정보: 메소드에 전달하는 매개변수, 메소드 실행 끝내고 돌아갈 곳,

                                                            힙에 들어 있는 객체에 대한 레퍼런드...

        ㆍ그런데 더이상 스택 프레임을 쌓을 수 없다면? StackOverflowError!

    - 스택의 사이즈를 조정하고 싶다면? - Xss1M

        ㆍ정말 최후의 수단

     

    * 스택 : 한 스레드마다 쓸 수 있는 공간

    - 스택에는 메서드 호출할 때 스택 프레임이 쌓인다. 

    - LIFO

     

    * 힙 : 객체들이 있는 공간, 가비지 컬렉터가 정리하는 공간

     

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

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

     

    - 1994년, 바바라 리스코프의 논문, "A Behavioral Notion of Subtyping" 에서 기원한 객체 지향 원칙

    - '하위 클래스의 객체'가 '상위 클래스 객체' 를 대체하더라도 소프트웨어의 기능을 깨트리지 않아야 한다.

       (semantic over syntacic, 구문 보다는 의미!)

     

    댓글

Designed by Tistory.