일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Orace 18c
- 오라클 캐릭터셋 변경
- Oracle 초기 사용자
- Oracle 테이블 띄어쓰기
- 오라클 캐릭터셋 조회
- Oracle Express Edition
- Oracle 사용자명 입력
- 윈도우 Oracle
- oracle 18c
- Oracle 18c HR schema
- Oracle 사용자명
- Oracle 윈도우 설치
- 오라클 캐릭터셋 확인
- 무료 오라클 설치
- Oracle 18c HR
- 비전공자를 위한 데이터베이스 입문
- oracle
- Oracle 테이블 대소문자
- 서평단
- ORA-00922
- ORA-12899
- 무료 오라클 데이터베이스
- Oracle 18c 설치
- ora-01722
- Today
- Total
The Nirsa Way
[Effective Java 3/E - 모든 객체의 공통 메서드] Item 10-1. equals는 일반 규약을 지켜 재정의하라 - equals를 재정의하지 않아야 할 4가지 상황 본문
[Effective Java 3/E - 모든 객체의 공통 메서드] Item 10-1. equals는 일반 규약을 지켜 재정의하라 - equals를 재정의하지 않아야 할 4가지 상황
KoreaNirsa 2025. 7. 14. 16:08
equals는 일반 규약을 지켜 재정의하라 - equals를 재정의하지 않아야 할 4가지 상황
일반적으로 equals는 아래의 상황 중 하나에 해당한다면 재정의 하지 않는 것이 좋습니다.
- 각 인스턴스가 본질적으로 고유하다.
- 인스턴스의 논리적 동치성(logical quality)을 검사할 일이 없다.
- 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
- 클래스가 private 이거나 pacakge-private이고 equals를 호출할 일이 없다.
1. 각 인스턴스가 본질적으로 고유하다.
값을 표현하는 클래스가 아니라 동작을 수행하는 개체일 경우 equals의 재정의하지 않는 것이 적절합니다. 예를 들어 Thread, Excutor, Rannable, Connection, Stream 같은 객체들은 각각의 인스턴스가 동작(특정 작업 흐름, 자원 결합)하므로 고유합니다.
이러한 동작을 수행하는 인스턴스는 본질적으로 고유한 상태이므로 서로 같다고 판단할 필요가 없을 때는 equals()를 굳이 재정의하지 않는것이 좋습니다.
Thread를 예로 들어 확인해보면 t1과 t2는 동작 주체가 다르므로 논리적 비교는 무의미 합니다. 즉, 이러한 경우에는 equals()를 굳이 재정의하여 사용할 일이 일반적으로 없을 것이며 만약 재정의가 필요하더라도 euqals의 본질적인 의미를 잘 생각하여 벗어나는 일이 없도록 해야 합니다.
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("Thread 1"));
Thread t2 = new Thread(() -> System.out.println("Thread 2"));
System.out.println(t1.equals(t2));
}
}
2. 인스턴스의 논리적 동치성(logical quality)을 검사할 일이 없다.
우선 논리적 동치성이 무엇인지부터 알아야 합니다. 논리적 동치성이란 "두 객체가 서로 다른 인스턴스라도 내부 상태 또는 표현하는 값이 같다면 '논리적으로 동등하다'라고 보는 것" 입니다. 대표적으로 String의 equals가 있습니다.
아래의 코드에서 a.euqals(b)의 경우 "표현하는 값이 같다면 논리적으로 동등하다"를 만족하므로 논리적 동치성 비교라고 볼 수 있습니다. 이러한 경우 논리적 동치성을 검사할 일이 있기 때문에 equals를 재정의하여 사용한 케이스입니다.
public class Test {
public static void main(String[] args) {
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false (참조 비교)
System.out.println(a.equals(b)); // true (논리적 동치성 비교)
}
}
이제 논리적 동치성을 검사할 일이 없는 케이스를 살펴볼텐데, 값의 동일성 보다는 동작의 주체, 식별성 등에 초점을 맞출 때 논리적 동치성이 필요 없을 것 입니다.
즉 String의 equals 처럼 "이 객체가 같은 값을 표현하는가?"에 초점을 두는것이 아니라, "서로 다른 객체인가?"가 더 중요한 경우 논리적 동치성이 필요 없는 것 입니다. 서로 다른 객체인지 비교가 중요한 경우 equals를 재정의 하지 않고 Object에 기본적으로 구현된 기능을 사용하는 것이 좋습니다.
아래와 같은 예시 코드를 볼 때 java.util.regex.Pattern을 사용하여 equals를 보면 재정의되어 있지 않음을 알 수 있습니다.
p1과 p2 모두 정규현 a*b를 컴파일하여 Pattern 객체를 생성하는데 p1.equals(p2)의 결과를 보면 false가 나옵니다. 이는 Object의 기본 구현을 그대로 사용하고 있음을 알 수 있으며, Object의 equals()는 기본적으로 참조 동일성 비교(==)이므로 서로 같은 객체인지를 확인하게 되는데, p1과 p2는 서로 다른 객체이므로 false가 나오게 됩니다.
public class Test {
public static void main(String[] args) {
Pattern p1 = Pattern.compile("a*b");
Pattern p2 = Pattern.compile("a*b");
System.out.println(p1.equals(p2)); // false
}
}
Pattern 클래스의 경우 내부의 값을 비교할 필요가 없는 객체임으로 equals를 재정의하지 않은것을 확인해보았습니다. 이렇게 논리적 동치성이 필요없는 경우 equals를 재정의 하지 않는것이 좋습니다. 만약, equals를 재정의하여 논리적 동치성을 검사하게 된다면 다른 equals와 근본적인 동작이 달라지므로 혼란을 야기할 수 있을 것 입니다.
3. 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
상위 클래스에서 이미 equals()를 재정의 하였으며 하위 클래스가 의미적으로 같은 비교 규칙을 따르기 때문에, 추가적인 로직 없이 충분히 적용 가능할 경우 euqlas()를 하위 클래스에서 굳이 재정의할 필요가 없다는 뜻입니다.
대표적으로 컬렉션 프레임워크에서 확인해볼 수 있는데 AbstractSet, AbstractList, AbstractMap과 같이 추상 클래스에서 equals()가 한 번만 재정의되며 상속받은 하위 클래스에서는 굳이 재정의를 하지 않고, 상위 클래스의 equals()를 그대로 사용하는 것을 볼 수 있습니다. 원하는 동작이 충분하므로 굳이 상속받은 하위 클래스에서 재정의가 필요없기 때문입니다.
HashSet을 예시로 보면 AbstractSet을 상속받고 있습니다.
AbstractSet의 추상 클래스를 확인해보면 equals()를 재정의한 코드를 확인할 수 있으며, HashSet에서는 equals()는 따로 재정의되지 않습니다.
즉 HashSet은 equals의 원하는 동작이 AbstractSet에 재정의해둔 로직과 완전히 일치하므로 굳이 재정의를 다시 하는것이 아니라 상위 클래스(AbstractSet)의 equals()를 그대로 사용하는 것 입니다.
클래스가 private 이거나 pacakge-private이고 equals를 호출할 일이 없다.
클래스가 내부 구현용으로만 사용되는 객체라면 equals가 외부에서 호출될 상황 자체가 없음으로 재정의할 필요가 없다는 뜻입니다.
아래의 예시 코드를 확인해보면 SessionHandle의 경우 내부에서 값을 저장하는 용도로만 사용되는 클래스이며, 외부에서 해당 클래스를 호출할 일도, 참조 동일성을 비교할 일도 없는 클래스입니다.
class SessionManager {
private static class SessionHandle {
private final String id;
SessionHandle(String id) {
this.id = id;
}
// 외부에 절대 노출되지 않고, 참조 동일성 비교 대상도 아님
}
public void createSession(String userId) {
SessionHandle handle = new SessionHandle(userId + "-" + System.currentTimeMillis());
}
}
equals() 메서드 호출 자체가 의미 없으며 개발자의 실수 또는 자동화 도구에 의한 오용을 사전에 방지하고 싶다면 아래와 같이 명시적으로 막아두는 방법도 있습니다.
class SessionManager {
private static class SessionHandle {
private final String id;
SessionHandle(String id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
throw new AssertionError("equals 호출 금지!");
}
}
public void createSession(String userId) {
SessionHandle handle = new SessionHandle(userId + "-" + System.currentTimeMillis());
}
}