2022. 9. 27. 14:53ㆍ독서
Java를 쓰는 분들은 equals메서드를 모르지 않을 것이다.
가장 널리 쓰이는 곳이 바로 String이다.
String의 equals 코드를 보자.
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
String.equals는 값의 논리적 동치성을 확인할 때 사용한다.
내부 코드를 들여다보면 실제로 비교는 String.equals에서 하는 것이 아니라
StringLatin1.equals에서 하는 것을 알 수 있다.
StringLatin1.eqauls 코드를 열어보면 아래와 같다.
@HotSpotIntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
return true;
}
return false;
}
이처럼 실제로는 String의 character 하나하나를 비교하는 것을 알 수 있다.
String 클래스처럼 우리는 많은 클래스를 생성하는데, 이때 생성된 클래스는 Object의 하위 클래스이기 때문에
모두 equals 메서드를 포함하고 있다.
그렇기 때문에 우리가 생성한 클래스에서 equals 메서드를 활용할 수 있다.
하지만 이때 문제가 생기는데, 재정의되지 않은 equals 메서드는 인스턴스의 동일성을 확인하는 Object.equals와 같다는 것이다.
이렇게 되면
Integer a = 10 과 Integer b = 10이 존재할 때,
a.equals(b)를 실행할 경우, false를 반환하게 된다.(Integer에서 equals를 재정의 하지 않았다는 가정하에)
그렇기 때문에 우리는 equals를 재정의하는 것이 상황에 알 맞는 equals를 사용하는 방법이다.
🧐재정의가 필요 없는 경우
책에서는 다음과 같은 경우에 equals 재정의가 필요하지 않다고 말한다.
- 각 인스턴스가 본질적으로 고유할 때
- 인스턴스의 '논리적 동치성'을 검사할 일이 없을 때
- 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 맞을 때
- 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없을 때
인스턴스가 본질적으로 고유하다는 것의 대표적인 예로 책에서는 Thread를 들었다.
나는 싱글턴 객체를 예로 들고 싶다.
싱글턴 객체는 MulitThread 환경에서도 반드시 하나의 인스턴스를 유지해야 하기 때문에 본질적으로 고유하다.
java.util.regex.pattern은 equals를 재정의해서 두 Pattern 인스턴스가 같은 정규표현식인지 검사할 일이 거의 없다.
또한 Object.equals로 해결이 가능하다.
🧐재정의가 필요한 경우
객체의 식별성, 즉 인스턴스가 같은지 확인이 필요한 경우가 아니라
논리적 동치성을 학인해야 하는 경우이며
상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 경우다.
Integer와 String을 생각하면 된다.
🧐equals의 일반규약
- 반사성: null이 아닌 모든 참조값 x에 대해서, x.eqauls(x)는 true다.
- 대칭성: null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)가 true면 y.equals(x)도 true다.
- 추이성: null이 아닌 모든 참조 값 x, y, z에 대해, x.equals(y)가 true이고, y.equals(z)도 true면 x.equals(z)도 true다.
- 일관성: null이 아닌 모든 참조 값 x, y에 대해, x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
- null-아님: null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 false다.
위의 규약을 지켜서 equals 메서드를 재정의해야 한다.
위의 규약들은 읽으면 모두 당연한 듯하지만 의외로 깨지기 쉬운 것들이다.
특히 추이 성의 경우 자칫하면 만족하기 쉽지 않다.
특히 상속을 하거나, 서브클래스를 만들 때 이것이 깨지기 쉬운데,
리스 코프 치환 원칙을 잘 지키면서 equals를 재정의 할 필요가 있다.
'독서' 카테고리의 다른 글
(나도코딩의 파이썬 입문) 2장. 자료형과 변수(p.39 ~ p.48) (0) | 2023.02.17 |
---|---|
(나도코딩의 파이썬 입문) 1장. Hello, 파이썬! (~p.38) (0) | 2023.02.16 |
(Effective Java) 의존 객체 주입(DI), 의존 객체 주입 패턴 (0) | 2022.09.20 |
(Effective Java) 인스턴스 생성 막기 (1) | 2022.09.20 |
(Effective Java) 싱글턴 패턴을 만드는 방법 + 싱글턴 패턴 (0) | 2022.09.20 |