지금까지의 내용을 종합해서 양질의 equals 메소드를 구현하는 방법을 정리하면 아래와 같다.
1. == 연사자를 사용해 입력이 자기 자신의 참조인지 확인한다.
자기 자신이면 true를 반환한다.
이는 단순한 성능 최적화용으로, 비교 작업이 복잡한 상황일 때 값어치를 한다.
2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
올바른 타입은 equals가 정의된 클래스인 것이 보통이지만, 가끔은그 클래스가 구현한 특정 인터페이스가 될 수도 있다.
어떤 인터페이스는 자신을 구현한 클래스끼리도 비교할 수 있도록 equals 규약을 수정하기도 한다.
이런 인터페이스를 구현한 클래스라면 equals 에서 클래스가 아닌 해당 인터페이스를 사용해야 한다.
Set , List , Map , Map.Entry 등의 컬렉션 인터페이스들이 여기에 해당한다.
3. 입력을 올바른 타입으로 형변환한다.
앞서 2번에서 instanceof 검사를 했기 때문에 이단계는 100% 성공한다.
4. 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.
모든 필드가 일치하면 true를, 하나라도 다르면 false 를 반환하다.
2단계에서 인터페이스를 사용했다면 입력의 필드 값을 가져올 때도 그 인터페이스의 메서드를 사용해야 한다.
타입이 클래스라면 접근권한테 따라 해당 필드에 직접 접근할 수도 있다.
float 와 double을 제외한 기본 타입 필드는 == 연산자로 비교하고,
참조 타입 필드는 각각의 equals 메소드로, float 와 double 필드는 각각 정적 메서드인 Float.compare(float , float) 와 Double.compare(double ,double) 로 비교한다.
float와 double을 특별 취급하는 이유는 Float.NaN , -0.0f , 특수한 부동소수 값등을 다뤄야 하기 때문이다.
Float.equals와 Double.equals 메소드를 대신 사용할 수도 있지만, 이메소드 들은 오토박싱을 수반할 수 있으니 성능상 좋지 않다.
배열 필드는 원소 각각을 앞서의 지침대로 비교한다.
배열의 모든 원소가 핵심 필드람ㄴ Arrays.eqausl 메소드들 중 하나를 사용하자.
때론 null도 정상 값으로 취급하는 참조 타입 필드도 있다.
이런 필드는 정적 메소드인 Objects.equals(object , object)로 비교해 NullPointerException 발생을 예방하자.
앞서의 CaseInsensitiveString 예처럼 비교하기가 아주 복잡한 필드를 가진 클래스도 있다.
이럴 때는 그 필드의 표준형(canonical form)을 저장해둔 후 표준형끼리 비교하면 훨씬 경제적이다. 이 기법은 특히 불변클래스에 제격이다.
가변 객체라면 값이 바뀔 때마다 표준형을 최신 상태로 갱신해줘야 한다.
어떤 필드를 먼저 비교하느냐가 equals의 성능을 좌우하기도 한다.
최상의 성능을 바란다면 다를 가능성이 더 크거나 비교하는 비용이 싼 필드를 먼저 비교하자.
동기화용 락 필드 같이 객체의 논리적 상태와 관련 없는 필드는 비교하면 안된다.
핵심 필드로부터 계산해낼 수 있는 파생 필드 역시 굳이 비교할 필요는 없지만, 파생 필드를 비교하는 쪽이 더 빠를 때도 있다.
파생 필드가 객체 전체의 상태를 대표하는 상황이 그렇다.
equals를 다 구현했다면 세가지만 확인해보면 된다
- 대칭적인가?
- 추이성이 있는가?
- 일관적인가?
반사성과 null-아님 조건도 만족해야 하지만 이 둘이 문제되는 경우는 별로없다.
마지막 주의사항으로는
- equals 를 재정의 할 땐 hashCode 도 반드시 재정의 하자
- 너무 복잡하게 해결하려 들지 말자
필드들의 동치성만 검사해도 equals 규약을 어렵지 않게 지킬 수 있다.
오히려 너무 공격적으로 파고들다가 문제를 일으키기도 한다.
일반적으로 별칭(alias)은 비교하지 않는 게 좋다.
File 클래스라면 심볼릭 링크를 비교해 같은 파일을 가르키는지를 확인하려 하면 안된다
- Object 외의 타입을 매개변수로 받는 equals 메소드는 선안하지 말자.
많은 프로그래머가 eqausl 를 다음과 같이 작성해놓고 문제의 원인을 찾아 헤맨다
=================================================================================================================
///잘못된 예 - 입력 타입은 반드시 Object여야 한다
public boolean equals(MyClass o){
}
=================================================================================================================
위 메소드는 Object.equals를 재정의 한게 아닌 다중정의 한 것이다.
기본 equals를 그대로 둔 채로 추가한 것일지라도, 이처럼 타입을 구체적으로 명시한 equals 는 오히려 해가 된다.
@Override 어노테이션의 오류를 내게 하고 보안측면에서도 잘못된 정보를 준다.
equals(hashCode포함)를 작성하고 ㅔㅌ스트 하는 일은 지루하고 이를테스트 하는 코드도 항상 뻔하다.
다행이 이 작업을 대신해줄 오픈소스가 있으니, 바로 구글이 만든 AutoValue 프레임워크다.
클래스에 어노테이션 하나만 추가하면 AutoValue가 이 메소드들을 알아서 작성해주며,
해당 어노테이션을 추가하면 개발자가 직접 작성하는 것과 근본적으로 똑같은 코드를 만들어 준다.
꼭 필요한 경우가 아니면 equals 를 재정의 하지 말자
많은 경우에 Object의 equals 가 원하는 비교를 정확히 수행해준다.
재정의해야 할 때는 그 클래스의 핵심 필드 모두를 빠짐없이, 다섯 가지 규약을 확실히 지켜가며 비교해야 한다
테스트를 위한 EqualsMain 클래스
=================================================================================================================
package EffectiveJava.item10;
public class EqualsMain {
public static void main(String[]args){
EqualsTestVO equalsTestVO1 = new EqualsTestVO(1,2,3,"김성철");
EqualsTestVO equalsTestVO2 = new EqualsTestVO(1,2,4,"김성철");
EqualsTestVO equalsTestVO3 = new EqualsTestVO(1,2,4,"양혜은");
EqualsTestVO equalsTestVO4 = new EqualsTestVO(1,2,4,"양혜은");
System.out.println("#### equalsTestVO1.equals(equalsTestVO2) : " + equalsTestVO1.equals(equalsTestVO2));
System.out.println("#### equalsTestVO3.equals(equalsTestVO4) : " + equalsTestVO3.equals(equalsTestVO4));
System.out.println();
//아래에 테스트를 위해서 equals 메소드를 재정의 한 부분에서 userName 을빼보자
//equalsTestVO2 와 equalsTestVO4 은 phone number는 같아도이름은 다르지만 같다고 나온다
System.out.println("#### equalsTestVO1.equals(equalsTestVO3) : " + equalsTestVO1.equals(equalsTestVO3));
System.out.println("#### equalsTestVO2.equals(equalsTestVO4) : " + equalsTestVO2.equals(equalsTestVO4));
}
}
=================================================================================================================
테스트를 위한 equals 를 재정의한 EqualsTestVO 클래스
=================================================================================================================
package EffectiveJava.item10;
import java.util.Objects;
public class EqualsTestVO {
int firstPhoneNumber;
int secondPhoneNumber;
int thirdPhoneNumber;
String userName;
public EqualsTestVO(int firstPhoneNumber, int secondPhoneNumber, int thirdPhoneNumber, String userName) {
this.firstPhoneNumber = firstPhoneNumber;
this.secondPhoneNumber = secondPhoneNumber;
this.thirdPhoneNumber = thirdPhoneNumber;
this.userName = userName;
}
public int getFirstPhoneNumber() {
return firstPhoneNumber;
}
public void setFirstPhoneNumber(int firstPhoneNumber) {
this.firstPhoneNumber = firstPhoneNumber;
}
public int getSecondPhoneNumber() {
return secondPhoneNumber;
}
public void setSecondPhoneNumber(int secondPhoneNumber) {
this.secondPhoneNumber = secondPhoneNumber;
}
public int getThirdPhoneNumber() {
return thirdPhoneNumber;
}
public void setThirdPhoneNumber(int thirdPhoneNumber) {
this.thirdPhoneNumber = thirdPhoneNumber;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
/*equals 재정의*/
@Override
public boolean equals(Object o){
//같은객체인지 비교
if(o ==this){
return true;
}
//타입 비교
if(!(o instanceof EqualsTestVO)){
return false;
}
//안에 들어있는 필드들의 값 비교
EqualsTestVO equalsTestVO = (EqualsTestVO)o;
return equalsTestVO.firstPhoneNumber == firstPhoneNumber && equalsTestVO.secondPhoneNumber==secondPhoneNumber
&& equalsTestVO.thirdPhoneNumber == thirdPhoneNumber && equalsTestVO.userName.equals(userName);
}
//IntelliJ 에서 생성한 equals
// @Override
// public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
// EqualsTestVO that = (EqualsTestVO) o;
// return firstPhoneNumber == that.firstPhoneNumber && secondPhoneNumber == that.secondPhoneNumber && thirdPhoneNumber == that.thirdPhoneNumber && Objects.equals(userName, that.userName);
// }
}
=================================================================================================================