Object는 객체를 만들 수 있는 구체 클래스이지만 기본적으로 상속해서 사용하도록 설계되어 있다. Object에서 final이 아닌 메서드(equals, hashCode, toString, clone, finalize)는 모두 재정의(overriding)을 염두에 두고 설계된 것이라 재정의 시 지켜야할 일반 규약이 명확히 정의되어 있다.
equals를 재정의 하지 않는 것이 최선이다. 아래의 경우에 해당한다면 equals를 재정의 할 필요가 없다.
ex 2) 동치성을 검사할 필요가 없는 경우 문자열 “Hello” == “Hello”
equals를 재정의 해야 한다면 반드시 Object 명세에 적힌 일반 규약을 따라야 한다.
반사성 : A.equals(A) == true
대칭성 : A.equals(B) == B.equals(A) ex) CaseInsensitiveString i) 대칭성 위배
// 대칭성 위배 54-55 페이지
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
//대칭성 위배
@Override
public boolean equals(Object obj) {
if (obj instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
}
if (obj instanceof String) {
return s.equalsIgnoreCase((String) obj);
}
return false;
}
public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String polish = "polish";
**System.out.println(cis.equals(polish));**
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
**System.out.println(list.contains(polish));**
}
}
ii) 수정된 equals 메서드(56페이지)
// 대칭성 위배 54-55 페이지
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
//수정된 equals 메서드(56페이지)
@Override
public boolean equals(Object obj) {
**return (obj instanceof CaseInsensitiveString) &&
(((CaseInsensitiveString) obj).s.equalsIgnoreCase(s));**
}
public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String polish = "polish";
**System.out.println(cis.equals(polish));**
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
**System.out.println(list.contains(polish));**
}
}
추이성 : A.equals(B) && B.equals(C), A.equals(C) ex) ColorPoint i) 대칭성 위배
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE
}
// 단순한 불변 2차원 정수 점(Point) 클래스 (56페이지)
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 obj) {
if (!(obj instanceof Point)) {
return false;
}
Point p = (Point) obj;
return p.x == x && p.y == y;
}
}
//Point에 값 컴포넌트(Color)를 추가 (56페이지)
public class ColorPoint extends Point{
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
//코드 10-2 잘못된 코드 - 대칭성 위배 (57페이지)
//Point는 부모 속성으로 비교하고 Color는 따로 속성 비교를 하면 되지 않을까?
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint)) {
return false;
}
return super.equals(obj) && ((ColorPoint) obj).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)**);
}
}
ii) 추이성 위배
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE
}
// 단순한 불변 2차원 정수 점(Point) 클래스 (56페이지)
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 obj) {
if (!(obj instanceof Point)) {
return false;
}
Point p = (Point) obj;
return p.x == x && p.y == y;
}
}
//Point에 값 컴포넌트(Color)를 추가 (56페이지)
public class ColorPoint extends Point{
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// 코드 10-3 잘못된 코드 - 추이성 위배! (57페이지)
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point)) {
return false;
}
// obj가 일반 Point면 색상을 무시하고 비교한다.
if (!(obj instanceof ColorPoint)) {
return obj.equals(this); //Point
}
// obj가 ColorPoint면 색상까지 비교한다.
return super.equals(obj) && ((ColorPoint) obj).color == color;
}
public static void main(String[] args) {
// 두 번째 equals 메서드(코드 10-3)는 추이성을 위배한다.
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));
}
}
iii) 리스코프 치환 원칙 위배
public enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE
}
// 단순한 불변 2차원 정수 점(Point) 클래스 (56페이지)
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// 잘못된 코드 - 리스코프 치환 원칙 위배 (59페이지)
// ColorPoint는 ColorPoint 끼리 같고, Point는 Point 끼리 같아야 하지 않을까?
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
Point p = (Point) obj;
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 Point( 1, 0);
//true를 출력한다.
System.out.println(onUnitCircle(p1));
//true를 출력해야 하지만, Point의 equals가 getClass를 사용해 작성되었다면 그렇지 않다.
System.out.println(onUnitCircle(p2));
}
}
리스코프 치환 원칙 : 상위 타입에서 동작 하는 코드는 하위 타입의 인스턴스를 주더라도 그대로 동작해야 한다.
ex) 대칭성 위배 예제. Timestamp, date
public static void main(String[] args) throws MalformedURLException {
long time = System.currentTimeMillis();
Timestamp timestamp = new Timestamp(time);
Date date = new Date(time);
//대칭성 위배! (60페이지)
System.out.println(date.equals(timestamp));
System.out.println(timestamp.equals(date));
}
iiii) Composition 사용(상속이 아닌 필드로 선언)
// 코드 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 obj) {
if (!(obj instanceof ColorPoint)) {
return false;
}
ColorPoint cp = (ColorPoint) obj;
return cp.point.equals(point) && cp.color.equals(color);
}
@Override
public int hashCode() {
return 31 * point.hashCode() + color.hashCode();
}
}
일관성 : A.equals(B) == A.equals(B)
null-아님 : A.equals(null) == false → 명시적으로 null을 검사하지 말고 묵시적으로 null을 검사하자.
@Overide
public boolean equals(Object o) {
if (!(o instanceof MyType)) { //null 검사가 이뤄진다.
return false;
}
MyType mt = (MyType) o;
}
equals 구현 방법