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
-
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