(Effective Java) equals는 일반 규약을 지켜 재정의하라

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를 재정의 할 필요가 있다.

 

 

 

 

반응형