Meet-Coder-Study / book-effective-java

📔 이펙티브 자바 스터디 저장소

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

ksy90101 opened this issue · comments

아이템10. equals는 일반 규약을 지켜 재정의하라
commented
public boolean equals(MyClass o) {
    ...
}

'타입을 구체적으로 명시한' equals는 오히려 해가 된다. 이 메서드는 하위 클래스에서 @Override 애너테이션이 긍정 오류(거짓 양성)를 내게 하고 보안 측면에서도 잘못된 정보를 준다.

이 두 가지 상황의 예시가 어떤 것이 있을까요? (이런 식으로 equals를 구현했던 적이 있었는데 잘못된 코드였네요😨)

  1. 하위 클래스에서 @OverRide 애너테이션이 긍정 오류(거짓 양성)을 내게 한다.
    -> 하위 클래스에서는 주소값을 비교하고 싶지만 상위 클래스에서 일부 필드에 대해 논리적인 비교를 해두었을 때 긍정 오류가 발생할 수도 있는 오류?

  2. 보안 측면에서도 잘못된 정보를 준다.
    -> ?

  1. 위 equals는 책에서도 나오듯이 Object#equals를 재정의한 것이 아니죠. 때문에 위 MyClass#equals가 true가 나오더라도 Java 프로그램/개발자가 흔히 아는 Object#equals가 아니다보니 긍정 오류라고 표현한 걸로 보여요
  2. 보안적인 측면은 아마 hashCode와 함께쓰이는 equals의 특성에 대한 내용같은데요. 이 참고 링크가 도움이 될거 같습니다.

1번 예시는 간단히 생각해봤는데요. (적절한 예시인지는 긴가민가하네요 ;;)

public interface LottoNumber {
}

public class UserLottoNumber implements LottoNumber {
    private final int number;

    public UserLottoNumber(int number) {
        this.number = number;
    }

    public boolean isSame(LottoNumber lottoNumber) {
        return this.equals(lottoNumber);
    }

    public boolean equals(UserLottoNumber userLottoNumber) {
        if (this == userLottoNumber) return true;
        if (userLottoNumber == null || getClass() != userLottoNumber.getClass()) return false;
        UserLottoNumber that = userLottoNumber;
        return number == that.number;
    }
}

public class ResultLottoNumber implements LottoNumber {
    private final int number;

    public ResultLottoNumber(int number) {
        this.number = number;
    }
}

LottoNumber는 따로 특별한 추상화를 하지않는 단순한 타입 홀더로 작동한다고 가정할께요.
여기서 유저가 발급받은 로또번호랑 결과로 나온 로또번호가 위와 같이 구현되어 있다고 볼께요.

이때 UserLottoNumber#equals는 책에서처럼 인자가 타입을 구체적으로 드러내도록 구현했습니다.

단, UserLottoNumber#isSameObject#equals를 사용하고 있는데요. 때문에 같은 객체를 UserLottoNumber#equalsUserLottoNumber#isSame에 같은 인자로 주어도 결과가 달라지는 상황이 나타나게 됩니다.

@Test
@DisplayName("equals 인자로 구체클래스 받는 경우")
void equals() {
    // given
    UserLottoNumber lottoNumber = new UserLottoNumber(1);

    // when
    boolean actual = lottoNumber.equals(new UserLottoNumber(1));

    // then
    assertThat(actual).isEqualTo(true);
}

@Test
void isSame() {
    // given
    UserLottoNumber lottoNumber = new UserLottoNumber(1);

    // when
    boolean actual = lottoNumber.isSame(new UserLottoNumber(1));

    // then
    assertThat(actual).isEqualTo(false);
}

경철님이 너무 잘 설명해주셨네요 ㅎㅎ
추가로 하위 클래스에서 @override 애너테이션이 긍정 오류(거짓 양성)을 내게 한다. 라는 말에 대해 제 의견을 적어보자면,

상위 클래스에서

public boolean equals(MyClass o) {
    ...
}

와 같이 equals를 잘못 정의하고 하위클래스에서 이를 @OverRide 한다면,
결국 잘못 정의한 equals를 오버라이드했는데도, 컴파일 에러는 안나겠죠?
이를 두고 긍정 오류(잘 될것이라고 예상했지만 잘 되지 못한 경우)라고 한 것 같습니다.

commented

@pkch93 @rockjoon
락준님 경철님 두 분 다 설명 감사합니다!

경철님이 예시로 들어주신 로또 넘버 예시와 테스트코드로 이해하는데 도움이 많이 되었습니다. 감사합니다! 결국 isSame과 equals가 다른 결과를 내는 것 뿐만 아니라 new UserLottoNum(1)과 new ResultLottoNum(1)의 equals도 (개발자가 원하지 않은) 다른 결과가 나오는 문제가 있겠네요.

소프트웨어 취약점을 볼 수 있는 사이트도 있었군요! 참고 링크도 감사합니다!!