Replies: 2 comments
-
제가 #23 equals를 재정의하려거든 hashCode를 재정의하라! 주제를 정리하다보니 equals 재정의에 대해 관심이 컸고 |
Beta Was this translation helpful? Give feedback.
0 replies
-
3번 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
0. 들어가며
equals 메서드는 두 개체가 같은지 확인하는데 사용됩니다.
Java 에서 두 개체가 같은지 확인하는 기준은
동일성
과동등성
두가지로 나누어집니다.0.1 동일성
동일성이란 두 개체가 완전히 같은지를 나타내는 성질입니다.
위 그림의 refVar2 와 refVar3 는 지역변수로서는 개별적으로 존재하지만, 같은 객체2를 가리킵니다.
즉, refVar2 == refVar3 이 성립하며 이는 두 변수 refVar2, refVar3 은 동일하다고 표현할 수 있습니다.
0.2 동등성
동등성
이란 두 개체가 논리적으로 같은지를 나타내는 성질입니다.위 예제 코드에 두 개의 String 인스턴스 nickName1 과 nickName2 가 있습니다.
nickName1 과 nickName2 는 서로 다른 인스턴스지만 값은 같습니다.
이 경우
동일
하진 않지만,동등
하다는 것을 알 수 있습니다.이렇게 되는 이유는 String 의 equals 는 인스턴스의 값이 같은지 확인하도록 재정의 되어있기 때문입니다.
그러면 Java 의 상위 클래스인 Object 클래스의 equals 메서드는 어떻게 되어있을까요?
Object 클래스의 equals 메서드는
==
연산자를 사용하여 두 인스턴스의동일성
을 비교합니다.즉, Java 에서는 기본적으로 equals 메소드가 두 인스턴스의
동일성
을 비교하고 있다고 생각할 수 있습니다.그런데 위의 String 의 경우처럼, 개발하면서
동등성
을 비교해야할 경우가 생기게 될 수 있습니다.이런 경우에는 equals 메서드를 재정의해서 사용하면되는데요.
하지만 논리적으로 같은지 확인하는 로직을 잘못 구현한다면 예기치 않은 결과를 초래할 수 있습니다.
이 때문에 이펙티브자바에서는 재정의하지 않아도 되는 경우에 대해 먼저 언급합니다.
1. 재정의 하지 않아도 되는 경우
각각의 경우에 대해 조금 더 자세히 살펴보겠습니다.
1.1 인스턴스가 본질적으로 고유한 경우
equals 메서드를 재정의하지 않았다면, 두 확인하는 로직이 기본입니다.
예를 들어 OS 관점에서 Thread 자체는 논리적, 물리적으로 독립적인 개체입니다.
그래서 이를 추상화하여 구현한 Java 의 Thread 또한 본질적으로 고유한 성질을 가져야 합니다.
즉, 해당 요소나 개념등이 본질적으로 고유한 성질을 가지고 있다면, 재정의할 필요가 없다고 생각하면 됩니다.
1.2 인스턴스가 논리적으로 같은지 검사할 일이 없는 경우
1.1 의 Thread 예시에서 이어서 생각해보겠습니다.
Thread 는 본질적으로 고유하니, 당연히 논리적으로 같은지 검사할 필요가 없습니다.
반대로 주민번호, 이름을 가진 Person 이라는 클래스가 있다고 생각해 봅시다.
우리나라는 주민번호가 같으면 같은 사람으로 취급합니다.
따라서, 이 경우에는 주민번호가 같은 사람은 논리적으로 같은 사람으로 취급할 수 있습니다.
즉, Person 인스턴스의 equals 는 주민번호가 같으면 동일한 인스턴스라는 논리적 의미를 구현하게 됩니다.
1.3 상위 클래스의 equals 가 하위 클래스에도 딱 맞는 경우
상위 클래스에서 구현한 equals 가 하위 클래스에도 동일하게 적용될 수 있다면, 굳이 재정의하지 않아도 됩니다.
아래는 HashSet, LinkedHashSet, TreeSet 등의 상위 클래스인 AbstractSet 클래스 입니다.
Set 내부의 구성요소가 모두 동일하면 논리적으로 같은 Set 이라고 취급됩니다.
Set 은 ‘중복되지 않는 요소들의 집합’ 입니다.
이를 확인하는데에는 AbstractSet 에 있는 equals 메서드만으로 충분하기 때문에 굳이 재정의 하지 않아도 됩니다.
그래서 HashSet, LinkedHashSet, TreeSet 이 구현체들은 실제로 모두 equals 를 재정의 하지 않았습니다.
1.4 클래스가 private, package-private 이고, equals 를 호출할 일이 없는 경우
클래스가 private 혹은 package-private 이라는 것은, 특정 클래스나 패키지의 내부에서만 사용할 클래스라는 의미죠.
그리고 해당 클래스는 equals 를 호출할 일이 없는데 혹시라도 불리는 것을 방지하기 위해서는 아래와 같은 방법이 있다고 합니다.
이 경우는 흔치않은 경우일 것 같습니다만, 이런 경우도 고려하더라 정도로만 생각하고 넘어가면 될 것 같습니다.
2. 정의해야하는 경우
그럼 반대로 정의해야하는 경우는 언제일까요?
동등성
을 확인해야 할 경우대표적인 예로 String 을 살펴보겠습니다.
단순히 인스턴스의
동등성
을 비교하는것이 아니라, 그 안에있는 값이 같은지를 확인합니다.하지만 앞서 언급한 것 처럼 equals 메소드는 Java 언어 내부적으로도 호출하여 사용하는 중요한 메서드입니다.
그래서 Java 에서는 equals 메소드를 구현할 때 지켜야할 규약을 정의해놓았습니다.
3. Object 클래스의 equals 규약
Object 클래스의 equals 메서드 주석을 보면 위와 같이 규약이 명시되어있습니다.
이를 간단히 풀어서 정리해보면 다음과 같습니다.
이 항목들을 하나씩 살펴보겠습니다.
3.1 반사성
객체는 자기 자신과 같아야 한다는 아주 기본적인 규약입니다.
앞서 1.3 에서 살펴봤던 AbstractSet 의 equals 메서드에서는 containsAll 이라는 메서드가 사용됩니다.
이는 AbstractCollection 에 정의되어 있는데요.
AbstractCollection 에서는 대상이 있는지 확인하는 로직에서 결국 equals 가 사용됩니다.
하지만 이를 잘못 구현해놓으면 중복이 안되어야하는 Set 에 중복이 발생할 수 있습니다.
3.2 대칭성
equals 를 재정의 하며 로직이 복잡해지면 대칭성은 자칫하면 어길 수 있는데요.
책에 나오는 예제는 다음과 같습니다.
CaseInsensitiveString 는 equals 에서 String 타입에 맞게 처리를 하여 true 를 반환합니다.
하지만 String 은 CaseInsensitiveString 에 맞게 처리하지 않아 false 를 반환합니다
이렇게 대칭성을 어기게될 수 있는 것이죠.
3.3 추이성
위 설명 그대로 이해하면 되며, 책에서는 Point 객체를 예제로 사용합니다.
(가독성을 위해 코드가 일부 수정되었습니다.)
위 처럼 p1, p2 는 동등하고 p2, p3 도 동등한데 p1, p3 은 동등하지 않습니다.
이 경우가 추이성을 어긴 경우입니다.
그래서 이펙티브자바에서는 이를 해결하기 위한 방법으로 내부에 view 를 갖는것을 제안합니다.
Point 를 상속을 하지않고, 연관 관계로 풀어내는 것이죠.
그리고 더 좋은 방법은 Point 클래스를 추상클래스로 만들어버린다면 이 문제가 해결된다고 합니다.
추이성 예제에서 발생한 문제들은 Point 와 ColorPoint 두 종류의 인스턴스를 비교하면서 발생했는데요.
Point 를 추상클래스로 만든다면 Point 를 인스턴스화 할 수 없으니 이러한 문제는 사라지기 때문입니다.
그리고 번외로 또 다른 예시를 한번 살펴보겠습니다.
1.2 에서 살펴봤던 Person 클래스를 다시 가져와봤습니다.
personA, personB 는 서로 다르고, personB, personAA 도 서로 다릅니다.
여기서 추이성을 착각하면 personA 와 personAA 도 달라야 하는것 아닌가 오해할 수 있는데요.
추이성은 같은때만 해당된다는 것을 유념해야 합니다.
3.4 일관성
이와 관련하여 java.net.URL 클래스가 이 규약을 위반하는 대표적인 사례라고 소개합니다.
URL 클래스의 equals 는 매핑된 호스트의 IP 주소를 비교하는데, 이는 네트워크 상태에 영향을 받기 때문입니다.
즉, equals 는 JVM 메모리상의 정보로만 동등성을 비교해야합니다.
3.5 null-아님
비교 대상이 null 이 아니어야 한다는 뜻입니다.
위 코드는 위에서 살펴본 Point 클래스의 equals 메소드입니다.
첫 번째줄의 instanceof 연산자는 첫번째 피연산자(o)가 null 이라면 false 를 반환한다고 합니다.
그래서 이러한 묵시적 null 검사를 활용하는것이 좋다고 합니다.
4. 정리
==
연산자를 사용해동일성
을 먼저 비교instanceof
연산자로 null 과 타입을 체크가격
,개수
)에서총주문금액
필드의 경우4.1 번외
Beta Was this translation helpful? Give feedback.
All reactions