이펙티브자바 10

참치돌고래·2021년 10월 3일
0

이펙티브 자바

목록 보기
9/21

equals의 재정의

equals를 재정의하지 않아도 되는 경우

  • 1.각 인스턴스가 본질적으로 고유하다.
  • 2.인스턴스의 '논리적 동치성'을 검사할 일이 없다.
  • 3.상위 클래스에서 재정의한 equals가 하위 클래스에 맞아 떨어진다.
  • 4.클래스가 private이거나 package-private이고 equals메소드를 호출할이 없다.
@Override public boolean equals(Object o){
	throw new AssertionError();
}

euqals 메소드를 재정의해야할 경우

  • 1.논리적 동치성을 비교해야할 때

    (String, Integer와 같은 경우 값을 비교하고 싶을 때.
    하지만, enum과 같이 객체가 하나만 생성되는 것이 보장되었을 때에는 equals 메소드를 재정의할 필요 없다. -> 논리적 동치성 == 객체 식별성

equals 재정의시 주의해야하는 경우

  • 1.반사성 : x.equals(x) = true ;
  • 2.대칭성 : x.equals(y) = y.equals(x);
  • 3.추이성 : x.eqauls(y) = y.equals(z) = x.equals(z);
  • 4.일관성 : 항상 일관된 값을 반환한다.
  • 5.NonNull : x.equals(null) = false;

반사성 관점에서의 문제

public final class CaseInsensitiveString {
    private final String;
    
    public CaseInsensitiveString(String s){
    	this.s = Objects.requireNonNull(s);
    }
    
    @Override 
    public boolean equals(Obejct o){
    	if( o instanceof CaseInsesitiveString){
        	return s.eqaulsIgnoreCase(
            	((CaseInsensitiveString) o).s);
        if (o instanceof String)
        	return s.equalsIgnoreCase((String) o);
        return false;
            )
        }
    }
}
CaseInsenitiveString cis = new CaseInsensitiveString("Polish");
String s = "Polish";

System.out.println("cis.equals(s) = " + cis.equals(s)); //true

System.out.println("s.equals(cis) =" + s.equals(cis)); //false

String 객체에서는 CaseIntensitiveString의 존재를 모르기 때문에 대칭성이 위반된다. 따라서, cis.equals(s)도 false로 나오게 다시 equals 메소드를 정의한다.

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

추이성 관점에서의 문제

1=2, 2=3, 1=2=3

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;
        
    }

}

위와 같이 Point객체끼리의 equals를 재정의하는 것은 간단하다. 하지만, point를 상속받는 클래스를 가정하여 생각해보자.

public class ColorPoint extends Point{
	private final Color color;
    
    pulbic ColorPoint(int x, int y, Color color){
    	super(x,y);
        this.color = color;
    }
    
    @Overrid public boolean equals(Object o){
    	if(!(o instanceof Point))
        	return false;
        if(!(o instanceof ColorPoint))
        	rturn false;
            
        return super.eqauls(o) && ((ColorPoint) o).color == color;
        
    }
}

위에서 정의한 equals 메소드는 색깔을 무시하고 비교를 하기 때문에, 추이성에 위반된다.

ColorPoint p1 = new ColorPoint(1,2,COLOR.RED);
Point p2 = new Point(1,2);
ColorPoint p3 = new ColorPoint(1,2,COLOR.BLUE);

p1.equals(p2); // true
p2.equals(p3); // true
p1.equals(p3); // false

해결법 : ColorPoint에 Point필드를 추가하여 추이성을 만족시키자.
상속 대신 컴포지션을 사용하자

public class ColorPoint{
	private final Point point;
    private Color color;
    
    public ColorPoint(int x, int y, Color color){
    	Point = new Point(x,y);
        this.color = Objects.requireNonNull(color);
    }
    
    public Point asPoint(){
    	return point;
    }
    
    @Override
    public boolean equals(Object o){
    	if(!(o instanceof ColorPoint)
        	return false;
        ColorPoint cp = (ColorPoint) o;
        return cp.point.equas(point) && cp.color.equals(color);
        
        
    }
}

equals 재정의 순서


  1. ==연산자를 통해 자기자신인지 검사하자.
  2. instanceof 를 통해 올바른 타입이 들어왔는지 확인
  3. 올바른 클래스로 형변환
  4. 필드들을 하나씩 같은지 검사.
  • 주의사항
  1. float, double 같은 경우는 compare함수를 통해 비교하자. float.equals 와 double.equals는 오토박싱을 수반하기 때문에 성능상 좋지않다.

  2. null을 허용하는 필드는 Object.equals()를 통해 nullPointException을 방지하자.

  3. 필드들을 비교해나갈 때, 비교하는 비용이 싼 필드들을 먼저 비교하자.

profile
안녕하세요

0개의 댓글