일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 셀렉티
- 앤드밀
- 압구정곱떡
- 압구정로데오맛집
- 토비의스프링
- EffectiveJava
- JUnit5
- @ExceptionHandler
- 이속우화
- 녹는다녹아
- 서오릉
- 인생맛집
- 경기족발
- 한우오마카세
- exceptionHandler
- @ControllerAdvice
- 데이트코스
- Java
- 데이트
- spring
- 상암동
- 아일랜드리솜
- 이펙티브자바
- 상암동맛집
- 맛집
- 서오릉맛집
- 인생소고기집
- 고기김치
- 닭껍질만두
- 청춘면가
- Today
- Total
Hyeonuk_.log
[Effective Java] 규칙8 equals를 재정의할 때는 일반규약을 따르라 본문
요즘은 벼르고 있던 '이펙티브자바'를 읽고 있다. 구매한지는 2년이 넘은 것 같다. 새로운 책이 나오기도 했지만 일단 집에 있는 것을 읽고 정리해보려 한다. 조금이나마 지식이 많아졌으면 좋겠다.
https://book.naver.com/bookdb/book_detail.nhn?bid=8064518
이펙티브 자바
JAVA, 더 깔끔하고, 정확하고, 안전하며, 재사용이 쉬운 코드로 만들자!『EFFECTIVE JAVA(이펙티브 자바)』는 효율적이면서도 잘 설계된 자바 프로그램을 구현하기 위해 알아야 할 지침 78가지를 소개
book.naver.com
equals 메서드는 재정의가 쉬워 보이지만 막상 재정의하려면 어려움을 느낀다. 롬복이 나오면서 많이 쉬워진 것을 느낀다.
아래의 조건 중에 하나라도 만족한다면, equals 재정의가 필요하지 않는 케이스이다.
1. 각각의 객체가 고유하다.
Thread가 해당된다. 이런 클래스는 Object의 equals를 그대로 사용해도 된다.
2. 클래스에 "논리적 동일성" 검사 방법이 있건 없건 상관없다.
java.util.Random 클래스는 같은 난수를 생성하는지 검사하는 메서드가 사용하는 사람들에게 필요하지 않을 것이라 생각하여 만들지 않았다. Object의 equals 계승만으로도 충분하다.
3. 상위 클래스에서 재정의한 equals가 하위 클래스에서 사용하기에도 적당하다.
대부분의 Set 클래스는 AbstractSet의 equals를 그대로 사용한드.
4. 클래스가 private 또는 package-private로 선언되었고, equals 메서드를 호출할 일이 없다.
논란의 소지가 있긴 하지만, 필자는 이러한 상황에 필수적으로 재정의 해야한다고 한다. (재정의하여 Exception을 던지는)
5. 최대 하나의 객체만 존재하도록 제한하는 클래스(enum, 규칙30)
이와 반대로 equals 메서드를 재정의하는 것이 바람직할 때는?
- 객체 동일성이 아닌 논리적 동일성의 개념을 지원하는 클래스일 때(Integer, Date), 그리고 상위 클래스의 equals가 하위 클래스의 필요를 충족하지 못할 때 재정의 해야한다.
equals를 정의할 때 준수해야하는 일반 규약(general contract)
1. 반사성(reflexive) : null이 아닌 참조 x가 있을 때, x.equals(x)는 true를 반환한다.
2. 대칭성(symmetric) : null이 아닌 참조 x와 y가 있을 때, x.equals(y)는 y.equals(x)가 true일 때만 true를 반환한다.
3. 추이성(transitive) : null이 아닌 참조 x,y,z에 대하여 x.equals(y)이고 y.equals(z)이면 x.equals(z)도 true이다.
4. 일관성(consistent) : null이 아닌 x, y가 있을 때, equals를 통해 비교되는 정보에 아무 변화가 없다면, x.equals(y) 호출 결과는 호출 횟수에 상관없이 같아야한다.
5. null이 아닌 x에 대해서, x.equals(null)은 항상 false이다.
[대칭성 위반 케이스]
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if(s == null) {
throw new NullPointerException();
}
this.s = 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;
}
.... // 생략
}
CaseInsensitive cis = new CaseInsensitive("Polish");
String s = "polish";
cis.equals(s)는 예상대로 true를 반환하지만
s.equals(cis)는 false를 반환하여 대칭성이 깨진다. 문제는 CaseInsensitive는 String에 대해 알지만, String은 CaseInsensitive에 대해 모르기 때문이다.
이 문제를 방지하려면 CaseInsensitiveString의 equals메서드가 String 객체와 상호작용하지 않도록 해야한다. 그렇게 하면 return 문을 하나만 사용하도록 리팩터링 할 수 있다.
@Override
public boolean equals(Object o) {
if(o instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(
((CaseInsensitiveString) o).s);
}
return false;
}
[추이성]
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 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 equals(Object o) {
if(!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
// 이하 생략...
}
public static void main(String args[]) {
Point point = new Point(1, 2);
ColorPoint colorPoint = new ColorPoint(1, 2, Color.BLACK);
// 대칭성 위반!!
System.out.println(point.equals(colorPoint)); // true
System.out.println(colorPoint.equals(point)); // false
}
// 아래와 같이 바꾸면 추이성이 깨진다.
@Override
public boolean equals(Object o) {
if(!(o instanceof Point))
return false;
// o가 Point객체면 색상을 비교하지 않음.
if(!(o instanceof ColorPoint))
return o.equals(this);
return super.equals(o) && ((ColorPoint) o).color == color;
}
public static void main(String args[]) {
Point point = new Point(1, 2);
ColorPoint colorPoint = new ColorPoint(1, 2, Color.BLACK);
ColorPoint colorPoint2 = new ColorPoint(1, 2, Color.RED);
// 추이성 위반!!
System.out.println(colorPoint.equals(point)); //true
System.out.println(point.equals(colorPoint2)); //true
System.out.println(colorPoint.equals(colorPoint2)); // false
}
위 소스에서 문제점을 해결할 방법은????
앞으로 살펴볼 규칙16에도 나오지만 "계승하는 대신 구성하라"라는 조언을 따르는 것이다.
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
if(color == null) {
throw new NullPointerException();
}
point = new Point(x,y);
this.color = 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);
}
// 이하 생략...
public static void main(String args[]) {
ColorPoint colorPoint = new ColorPoint(1, 2, Color.BLACK);
ColorPoint colorPoint2 = new ColorPoint(1, 2, Color.RED);
ColorPoint colorPoint3 = new ColorPoint(1, 2, Color.BLACK);
System.out.println(colorPoint.equals(colorPoint2));
System.out.println(colorPoint.equals(colorPoint3));
}
}
훌륭한 equals 메서드를 만들기 위한 지침들
1. == 연산자를 사용하여 equals의 인자가 자기 자신인지 검사하라. 만일 그렇다면 true를 반환하라. 단순히 성능 최적화를 위한 것이다. 객체 비교 오버헤드가 클 경우에 위력을 발휘한다.
2. instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사하라. 그렇지 않다면 false를 반환하라.
3. equals의 인자를 정확한 자료형으로 반환하다. 그 앞에 instanceof를 사용한 검사 코드를 두었으므로, 형 변환은 반드시 성공할 것이다.
4. "중요" 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다.
5. equals 메서드 구현을 끝냈다면, 추이성, 대칭성, 일관성의 세 속성이 만족되는지 검토하라. (단위테스트도 진행, null에 대한 비동치성도 준수)
6. equals를 구현할 때는 hashCode도 재정의하라.(규칙9)
7. equals 메서드의 인자형을 Object에서 다른 것으로 바꾸지 마라.
이 책을 십년전에 알았더라면 분명 좋았을 것이다.
나라면 자바 책은 필요로 하지 않을 거라고 생각하는 이들도 있겠지만
이 책만큼은 다르다
-제임스 고슬링-
자바 언어와 객체지향 언어를 어떻게 사용하는 것이 좋은지 알려주는
훌륭한 조언들로 가득한 최고의 책
-기라드 브라차-
저절로 겸손해지는 문장들이다. 앞으로 끊임없이 공부하고 기록하자
'Dev_.log > Java' 카테고리의 다른 글
[Effective Java] 규칙 9 equals를 재정의할 때는 반드시 hashCode도 재정의 하라 (0) | 2022.02.25 |
---|---|
알고 사용하자 Static이란 (0) | 2022.02.20 |