티스토리 뷰
equals를 재정의 하고 싶다면 일반 규약을 지켜야 한다 일반 규약은 다음과 같다
1. 반사성, reflexibility
x가 null이 아닐 때 모든 x에 대하여 x.equals(x)는 true
2. 대칭성, symmetry
null이 아닌 두 값 x, y가 있을 때, x.equals(y) == y.equals(x)가 true라면 그 반대도 성립해야 한다
3. 추이성, transitivity
null이 아닌 세 값 x, y, z가 있을 때, x.equals(y) == true, y.equals(z) == true면 x.equals(z) == true
4. 일관성, consistency
x, y의 값이 변하지 않을 때 x.equals(y) == true라면 언제 호출하더라도 false로 변해선 안 된다
5. null 아님, non-null
null이 아닌 x, y가 있을 때 x.equals(null), y.equals(null)은 항상 false
천천히 생각해보면 다 당연한 얘기인데 이를 왜 지키라고 했을까?
개발 방식에 따라 일반 규약을 개박살 낼 방법이 존재하기 때문이다
객체 조상님 Object에서 equals는 메모리 주소를 비교하는 방식으로 정의되어 있다
실제로 사용할 때는 메모리 주소 비교가 아닌 값의 비교가 필요할 수 있는데 이때 equals()를 재정의해 사용한다
반사성을 박살 내려면 equals 비교 시에 수행 시마다 달라지는 랜덤 값으로 비교하게 하면 된다
반사성은 사실 마음먹고 equals를 개판으로 짜야겠다 하는 것이 아니라면 깨기가 더 어렵다
대칭성은 이 보다는 쉽게 깰 수 있다
책의 예제로 나온 것처럼 String을 필드로 가지고 있는 BlahBlahString 클래스를 만든 후
BlahBlahString 클래스에서는 필드를 이용하여 String과 비교를 수행하면 true가 나올 수 있는데
String의 equals는 타입 비교를 먼저 때리기 때문에 BlahBlahString은 타입에서 걸러져 false가 뜬다
String은 이러한 문제를 걱정해서 상속하지 못하도록 final class로 만들었으나
참조로만 가지고 있어도 깨부술 수 있다 이건 조심할 필요가 있어 보인다
추이성은 대칭성과 마찬가지로 상속이나 참조로 깰 수 있다
상속받고 난 클래스에서 필드 몇 개 추가한 후 그놈들을 가지고 상위 클래스와 비교 시
상위 클래스에서 equals는 성공할 수 있으나 하위 클래스에서 equals 때리면 상위 클래스에는
비교할 필드 자체가 없어 실패할 것이다
일관성은 가변 필드로 깰 수 있다
가변 필드는 언제라도 변할 수 있으니 A == B가 항상 성립함을 보장할 수 없다
가변이야 당연히 바뀔 수 있으니 그렇다 치고 불변 객체는 어떨까?
이 악물고 깰라면 깰 수 있다 Reflection이나 상속으로 깨버리는 것인데
이게 실제 상황에서 일어날 수 있는 일인지 감이 잘 안 온다 내부에서 프로그램을 망치려고 하는
작업이 들어올 리는 없고 수행된다면 외부 코드가 원격 수행되는 것인데 xss, csrf 등 고려해서
이런 걸 다 막지 않나 싶은데 최근 Log4j2 취약점이 외부 코드가 삽입된 후 수행되던 문제였으니 어쨌든 주의하자
Non-null 조건은 if (arg == null) return true; 로 간단하게 깰 수 있다
이상으로 깨는 조건들을 알아봤는데 현실적으로 주의해야 할 부분은 대칭성, 추이성, 일관성이다
얘네 셋의 공통점은 기본 클래스를 확장했을 때의 문제점이다
따라서 타입 비교를 엄격히 해 instanceof 조건 검사해주고 특별한 이유가 없다면 상&하위 클래스 간 비교를 허용하지 말자
equals 재정의가 필요한 경우에는 5가지 규약을 구글링 한 후 보면서 작성하자
IDE가 기본적으로 만들어주는 equals 또한 문제가 될 수 있다
JPA 사용 시 Entity <-> Proxy 간 비교될 때 발생할 수 있는 문제로
연관관계를 1 : N으로 설정하고 Lazy Loading 갈기면 프록시로 조회된다
프록시와 기존 엔티티를 비교하려고 equals를 수행해보면 필드 직접 접근으로 만든 equals는 값 비교에 실패한다
Proxy는 직접 필드를 가지고 있는 것이 아닌 실제 엔티티에 대한 참조를 가지고 메서드나 값을 위임하는 녀석이기 때문이다
따라서 getter를 가지고 만들어주는 equals를 사용해야 한다
이런 위험이 있을까 봐 엔티티에서 getter 기준으로 만들어주는 equals 쓰고 있었는데
lombok이 제공하는 @EqualsAndHashCode 가 getter 사용해서 만들어 주는 걸 확인하고 얘로 바꿔놨다
Entity에서 @EqualsAndHashCode 사용 시 성능 저하가 있을 수 있어 추천되지 않는다는 IDE 경고가 뜬다
이 부분은 나중에 한번 파봐야겠다 어찌 됐든 lombok > IDE > 직접 작성 순으로 추천되는 방식이다
'Java > Effective Java' 카테고리의 다른 글
[Item12] toString 정말 필요한가 (0) | 2022.02.19 |
---|---|
[Item11] @EqualsAndHashCode (2) | 2022.02.19 |
[Item09] 뭐든 수준 높게 사용하자 (0) | 2022.02.15 |
[Item08] finalizer, cleaner를 알아보지 말자 (0) | 2022.02.14 |
[Item07] 최고 JVM아 고맙다 (0) | 2022.02.12 |