본문 바로가기
JAVA/Effective Java

item 10. equals는 일반 규약을 지켜 재정의하라

by Garonguri 2022. 4. 19.
728x90

* 전 page에 대해 공부하였으나, 모든 책의 내용을 다 적기 보다는 꼭 필요한 내용을 추려 이해하기로 하였음 !!! (많아서)

 

equals method란?

-> call by value로 객체 값을 비교하는 메소드 (==는 call by reference로 주소값 비교)

 

어떤 경우에 equals를 재정의하고, 하지 말아야 할까?

 

[equals를 재정의하지 않는 경우]

- 각 인스턴스가 본질적으로 고유한 경우 (값 클래스가 아닌 경우에 해당한다. ex) thread)

- 인스턴스의 논리적 동치성을 검사할 필요가 없는 경우

- 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는 경우

- 클래스가 private이거나 package-private이고, equals method를 호출할 일이 없는 경우

- 인스턴스 통제 클래스와 같은 경우 (Enum, Singleton 등은 인스턴스가 한개만 만들어지기 때문에 논리적 동치성과 객체 식별성의 의미가 같다.)

 

[equals를 재정의하는 경우]

- 논리적 동치성을 확인해야 하는 경우이나, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 경우

(주로 값 클래스들이 해당됨 : Integer, String ...)

본인같은 경우에는 객체를 만들 때 특정 멤버변수를 통해 객체를 구별하고 싶을 때 equals를 재정의해서 사용함.

동치 관계 : 논리학이나 수학, 특히 집합론에서 쓰이는 도구 중 하나. 어떤 두 객체가 서로 "같다"는 개념을 추상화한다. 이항 관계의 일종.

 

 

[equals를 재정의 일반 규약]

-> 반드시 따라야 한다.

 

- 반사성(reflexivity)

/*객체는 자기 자신과 같아야 한다.*/
x.equlas(x) == true
public class item10 {
    public static void main(String[] args) {

        /*확인 방법*/
        //인스턴스를 컬렉션에 넣어 contains를 호출해 true가 나오면 지킨 것!
        List<Study> studyList = new ArrayList<>();
        Study study = new Study(830, 4, "CSbreak");
        studyList.add(study);

        System.out.println(studyList.contains(study));
    }

    public static class Study{
        int time;
        int member;
        String Teamname;

        public Study(int time, int member, String Teamname) {
            this.time = time;
            this.member = member;
            this.Teamname = Teamname;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Study study = (Study) o;
            return time == study.time && member == study.member && Objects.equals(Teamname, study.Teamname);
        }

        @Override
        public int hashCode() {
            return Objects.hash(time, member, Teamname);
        }
    }
}

- 대칭성(symmetry)

/*서로에 대한 동치 여부에 똑같이 답해야 한다.*/
x.equals(y) == y.equals(x)

 

  • 위의 예에서, Teamname이 같으면 (대소문자를 구분하지 않고) 같은 인스턴스라고 반환하는 객체가 있다고 정의해보자.
  • equals가 일반 문자열과 비교를 시도하게 된다면, Study class의 String의 equals는 일반 String을 알고있지만, 일반 String의 equals는 Study class의 존재를 모른다. 따라서 두 equals method가 다른 값을 반환한다.
  • 위의 예와 같이 collection에 넣어 contains를 호출해도 JDK 버전에 따라 다른 값을 반환한다.
//-> 해결 방안 : equals를 String과 Study class의 equals를연동하겠다는 꿈을 버린다.
@Override
public boolean equals(Object o) {
    if (null == o) {
        return false;
    }
    if (o instanceof Study) {
        return Teamname.equalsIgnoreCase(((Study)o).Teamname);
    }
    return false;
}

- 추이성(transitivity)

x.equals(y) == true //이고
y.equals(z) == true //이면
x.equals(z) == true //이다.

 

  • ex) 상위 클래스에 없는 새로운 필드를 하위 클래스에 추가하는 상황 (equls 비교에 영향)
  • 구체 클래스를 확장해 새로운 필드를 추가하면서, 대칭성과 추이성을 모두 해결하며 equals를 재정의할 수 있는 방법 ?
  • ==> 존재하지 않는다.
  • 해결 방법 : 상속 대신 컴포지션을 사용한다. (item18) 상속하는 대신 하위 클래스의 private 필드로 두고, view 메서드를 public으로 추가하는 식으로 해결할 수 있다.

 

- 일관성(consistency)

x.equals(y) == true;
x.equals(y) == true;
//     ...  
x.equals(y) == true;
//언제나 값은 같아야 한다.
  • 가변 객체의 경우 비교 시점에 따라 같을수도, 다를 수도 있는 반면, 불변 객체는 한번 다르면 끝까지 다르다.
  • 클래스를 만들 때 불변 클래스로 만드는 것이 나을지 심사숙고하자.(item17)
  • 가변/불변에 관계 없이, equals의 판단에 신뢰할 수 없는 자원이 껴있다면 일관성을 파괴할 수 있다.
  • -> 해결 방법 : 
  • 항상 메모리에 존재하는 객체만을 사용하는 deterministic(결정적)계산만 수행해야 한다.

 

- Not null

모든 객체는 Null과 같지 않아야 한다.
x.equals(null) == false;

 

 

올바른 equals method 구현 방법

  1. == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
    • 단순한 성능 최적화용으로 비교 작업이 복잡한 상황일 때 값어치를 한다.
  2. instanceof 연산자로 입력이 올바른 타입인지 확인한다.
    • 묵시적 null 체크를 할 수 있다.
    • Set, List, Map 등의 collection interface들이 해당된다.
  3. 입력을 올바른 타입으로 형변환한다.
    • 2번에서 instanceof 검사를 했기 때문에 100% 성공한다.
  4. 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 하나씩 검사한다.
    • 2단계에서 인터페이스를 사용했다면 입력 필드값을 가져올 때 인터페이스 메소드를 사용해야 한다.

 

주의 사항

  • 기본 타입은 == 으로 비교한다.
  • double, float는 Double.compare(), Float.compare()을 이용해 검사해야 한다. -> 부동소수점을 다뤄야 하기 때문이다.
  • 배열의 모든 원소가 핵심 필드이면 Arrays.equals 메서드들 중 하나를 사용한다.
  • null이 정상 값으로 취급할 때는 OBjects.equals()를 이용해 NPE를 예방해야 한다.
  • 비교하기 복잡한 필드를 가진 클래스는 표준형을 저장해둔 후 표준형을 비교한다.
  • 최상의 성능을 바란다면 다를 가능성이 더 크거나 비교하는 비용이 싼 필드를 먼저 비교한다.
  • 동기화용 락 필드 같이 객체의 논리적 상태와 관련 없는 필드는 비교하면 안 된다.
  • 파생 필드가 객체 전체 상태를 비교하는 경우 파생 필드를 비교하는 것이 빠르다.
  • equals를 재정의할 땐 hashCode도 반드시 재정의 한다.
  • Object 타입을 매개변수로 받는 equals 메서드는 선언하지 말자.
728x90

댓글