[Java] Object
정의
java.lang.Object 는 Java 의 모든 클래스의 최상위 부모 (root) 클래스. extends 가 명시되지 않은 클래스는 컴파일러가 자동으로 extends Object 를 붙인다. 인터페이스는 클래스가 아니라 Object 를 직접 상속하지는 않지만, 인터페이스 구현체는 결국 Object 의 메서드를 호출 가능.
이 단일 root 가 Java 의 collection, reflection, equality, threading 의 기초가 된다. Object 의 메서드를 이해하지 않고는 Collection, HashMap, Iterable 어느 것도 정확히 쓸 수 없다.
핵심 메서드 11개
| 메서드 | 역할 | 자주 override? |
|---|---|---|
equals(Object) | 동등성 비교 | ✓ |
hashCode() | 해시 코드 (Map/Set 용) | ✓ |
toString() | 문자열 표현 | ✓ |
getClass() | 런타임 클래스 (final) | ✗ |
clone() | 얕은 복사 (protected) | △ |
finalize() | GC 직전 호출 (deprecated) | ✗ |
wait(), wait(long), wait(long, int) | 모니터 대기 (final) | ✗ |
notify(), notifyAll() | 모니터 깨우기 (final) | ✗ |
final 인 것은 override 불가, 나머지는 가능.
equals / hashCode 의 5가지 규약
equals(Object) 를 override 하면 hashCode() 도 반드시 override 해야 한다. JCF (Java Collections Framework) 의 HashMap, HashSet 이 두 메서드의 일관성에 의존하기 때문.
equals(Object) 규약:
- 반사 (reflexive):
x.equals(x) == true - 대칭 (symmetric):
x.equals(y)이면y.equals(x) - 추이 (transitive):
x.equals(y) && y.equals(z)이면x.equals(z) - 일관 (consistent): 객체가 변하지 않으면 같은 결과
- null 비교:
x.equals(null) == false
hashCode() 규약:
equals가 true 인 두 객체는 같은 hashCode 를 반환해야 한다 (역은 아님)- 객체가 변하지 않으면 같은 hashCode 반환
public class Point {
private final int x;
private final int y;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
IMPORTANT
equals 만 override 하고 hashCode 를 안 하면 HashMap, HashSet 이 조용히 잘못 동작 한다. 같은 점이 두 번 들어가거나, 넣은 값을 못 찾는 식. @Override annotation 으로 컴파일러 검사를 유도하라.
toString, 디버깅의 기본
기본 구현은 ClassName@hexHashCode 같은 형태로 거의 쓸모없다. 자기 자신을 의미있게 표현하도록 override 권장.
@Override
public String toString() {
return "Point(%d, %d)".formatted(x, y);
}
Java 16+ 의 record 는 자동 생성. lombok 도 @ToString 으로 생성.
wait / notify, monitor 패턴
wait(), notify(), notifyAll() 은 모니터 락 (monitor lock) 위에서만 호출 가능. 그래서 반드시 synchronized 블록 안에서 호출해야 한다.
synchronized (obj) {
while (!condition) {
obj.wait(); // 락 해제하고 대기
}
// condition 충족, 처리
}
// 다른 스레드
synchronized (obj) {
condition = true;
obj.notifyAll(); // 대기 중인 모든 스레드 깨움
}
규칙:
synchronized밖에서 호출하면IllegalMonitorStateException- 조건은 항상
if가 아닌while로 검사 (spurious wakeup 가능) notify보다notifyAll이 안전 (단일 wakeup 으로 누가 깨어났는지 알 수 없음)
CAUTION
새 코드에서는 wait/notify 대신 ReentrantLock + Condition, CountDownLatch, Semaphore, BlockingQueue 같은 고수준 동기화 도구 를 권장한다. wait/notify 는 미묘한 버그 (spurious wakeup, lost notification) 를 만들기 쉽다.
모든 컬렉션의 root 가 Object 인 이유
JCF 의 메서드는 거의 모두 Object 타입을 받는다.
public interface Collection<E> {
boolean contains(Object o); // ← Object, not E
boolean remove(Object o); // ← Object, not E
boolean equals(Object o);
}
이는 타입 erasure 와 invariance 때문이다. List<Integer>.contains(double) 도 컴파일은 되지만 항상 false. 이런 미묘한 함정의 원인. Joshua Bloch Effective Java Item 65 가 이를 다룬다.
getClass, Reflection 의 입구
getClass() 는 final 이라 override 불가. 런타임에 정확한 클래스를 알려준다.
Object x = "hello";
Class<?> c = x.getClass(); // String.class
String name = c.getName(); // "java.lang.String"
Method[] methods = c.getMethods(); // public 메서드 목록
Reflection, Annotation 처리, Serialization 등의 진입점. Spring 의 DI 컨테이너도 결국 getClass() 기반으로 빈을 만든다.
clone, 거의 안 쓰는 것이 답
Object.clone() 은 protected 라 외부에서 호출 못 한다. 사용하려면 Cloneable 인터페이스를 구현하고 clone() 을 public 으로 override 해야 한다. 그런데 깊은 복사 (deep copy) 는 직접 처리해야 하므로 함정이 많다.
IMPORTANT
대안: 복사 생성자 (new Foo(other)) 또는 정적 팩토리 메서드 (Foo.copyOf(other)) 가 훨씬 명확하다. Joshua Bloch Effective Java Item 13 이 “clone 은 피해라” 라고 단정.
finalize, deprecated
finalize() 는 GC 가 객체를 회수하기 직전에 호출되도록 설계됐지만:
- 호출 시점 불확실, 영원히 안 불릴 수도 있다
- 성능 저하, finalizer 가 있는 객체는 GC 가 두 번 본다
- 보안 취약점, finalizer attack
- Java 9+ 에서
@Deprecated, Java 18+ 부터--finalization=disabled옵션
대안: AutoCloseable + try-with-resources, Cleaner 클래스 (java.lang.ref.Cleaner).
Object 만 받는 메서드와 제네릭
Collection.contains(Object) 처럼 제네릭이 도입된 뒤에도 Object 인자가 남아있다. 이는 하위 호환성 때문, JDK 1.0 코드가 그대로 동작해야 했다. Joshua Bloch 등 설계자들도 이를 후회한다고 밝힌 적이 있다.
참고
- Iterable
- Collection
- fail-fast iterator
- Joshua Bloch, Effective Java (3rd ed.), Items 10-14 (equals, hashCode, toString, clone, comparable)
- Brian Goetz, Java Concurrency in Practice, §14.2 Using Condition Queues
이 글의 용어 (4개)
- [Java] Collectionjava
- 정의 는 그룹으로 묶인 객체들을 표현하는 최상위 인터페이스. JCF (Java Collections Framework) 의 입구이자, / / / 모두 이를 확장한다. 자체는 직접…
- [Java] Fail-Fast Iteratorjava
- 정의 Fail-Fast Iterator 는 순회 도중 컬렉션의 구조가 변경되면 즉시 을 던지는 iterator. , , 등 대부분의 컬렉션의 iterator 가 이 방식. 목적은…
- [Java] Iterablejava
- 정의 는 루프로 순회 가능한 모든 타입의 최상위 인터페이스. 단 하나의 추상 메서드, 를 정의한다. 인터페이스가 을 extends 하므로 , , , 등 모든 컬렉션이 자동으로 대…
- [Java] ReentrantLockjava
- 정의 는 키워드와 같은 상호 배제 (mutual exclusion) 를 제공하는 클래스 기반 락. JSR-166 (Java 5) 에서 추가됐다. "재진입 (reentrant)" …
이 개념을 다룬 위키 페이지 (25)
- wiki[Java] ArrayDeque
- wiki[Java] ArrayList
- wiki[Java] BlockingQueue
- wiki[Java] Collection
- wiki[Java] ConcurrentHashMap
- wiki[Java] ConcurrentLinkedDeque
- wiki[Java] ConcurrentLinkedQueue
- wiki[Java] ConcurrentSkipListMap
- wiki[Java] ConcurrentSkipListSet
- wiki[Java] CopyOnWriteArrayList
- wiki[Java] CopyOnWriteArraySet
- wiki[Java] Deque
- wiki[Java] HashMap
- wiki[Java] HashSet
- wiki[Java] Iterable
- wiki[Java] LinkedHashMap
- wiki[Java] LinkedList
- wiki[Java] List
- wiki[Java] Map
- wiki[Java] PriorityQueue
- wiki[Java] Queue
- wiki[Java] Set
- wiki[Java] TreeMap
- wiki[Java] TreeSet
- wiki[Java] Vector
💬 댓글