티스토리 뷰

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 > 직접 작성 순으로 추천되는 방식이다

댓글
링크
글 보관함
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Total
Today
Yesterday