-
아이템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, 구문 보다는 의미!)
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템 12. toString을 항상 재정의하라 (0) 2023.06.18 아이템11. equals를 재정의하려거든 hashCode도 재정의하라 (0) 2023.06.17 아이템9. try-finally 보다 try-with-resources 를 사용하라. (0) 2023.06.07 아이템8. finalizer와 cleaner 사용을 피하라 (0) 2023.06.04 아이템7. 다 쓴 객체 참조를 해제하라. (0) 2023.06.01