java.lang 패키지에 포함된 java.lang.Object 클래스는 자바 클래스 계층 구조의 뿌리(root), 즉 최상위 수퍼 클래스입니다. 자바에서 기본으로 제공하는 모든 사전 정의 클래스와 개발자가 직접 작성한 사용자 정의 클래스는 예외 없이 Object 클래스의 하위 클래스입니다. 따라서 별도로 상속을 선언하지 않아도 모든 클래스는 자동으로 Object 클래스를 상속받게 됩니다.
Object 클래스가 수퍼 클래스인 이유
1. 재사용성(Reusability)
- 모든 객체는 본질적으로 11가지 공통 속성을 가지며, 원래라면 모든 자바 개발자가 이를 매번 직접 구현해야 합니다.
- 개발자의 부담을 덜어주기 위해 SUN(현 Oracle)은 이 11가지 속성을 11개의 메서드로 미리 구현한 Object 클래스를 제공했습니다.
- 이 메서드들은 모든 하위 클래스에 공통으로 적용되는 일반적인 로직을 담고 있으며, 하위 클래스의 요구 사항에 맞지 않는 경우 언제든지 오버라이딩하여 재정의할 수 있습니다.
2. 런타임 다형성(Runtime Polymorphism)
- 모든 객체가 Object 타입으로 업캐스팅될 수 있기 때문에, 어떤 종류의 클래스 객체든 인수로 받거나 반환값으로 돌려줄 수 있는 단일 메서드를 작성할 수 있습니다. 이를 통해 런타임 다형성을 손쉽게 구현할 수 있습니다.
모든 클래스 객체가 공통으로 가지는 기능
Object 클래스는 모든 객체가 필요로 하는 기본 기능을 다음과 같은 메서드로 제공합니다.
객체 비교
public boolean equals(Object obj)— 두 객체가 논리적으로 동일한지 비교합니다.
해시코드 조회
public int hashCode()— 객체의 해시코드 값을 반환합니다.
런타임 클래스 참조 조회
public final Class getClass()— 실행 시점의 클래스 객체 참조를 반환합니다.
문자열 형태의 객체 정보 반환
public String toString()— 객체의 정보를 문자열로 변환하여 반환합니다.
객체 복제
protected Object clone() throws CloneNotSupportedException— 객체의 복사본을 생성합니다.
객체 정리 및 리소스 해제
protected void finalize() throws Throwable— 객체가 소멸되기 전에 정리 작업을 수행합니다.
스레드 대기(wait)
public final void wait() throws InterruptedException— 다른 스레드가 notify()를 호출할 때까지 현재 스레드를 대기시킵니다.public final void wait(long timeout) throws InterruptedException— 지정된 시간(밀리초) 동안만 대기합니다.public final void wait(long timeout, int nano) throws InterruptedException— 밀리초와 나노초 단위로 지정된 시간 동안 대기합니다.
객체 락 알림(notify)
public final void notify()— 객체의 락이 사용 가능해졌음을 대기 중인 스레드 하나에게 알립니다.public final void notifyAll()— 해당 객체의 락을 기다리는 모든 대기 스레드에게 알립니다.
예제 코드
다음 예제는 Object 클래스의 주요 메서드(equals, hashCode, toString, clone)를 오버라이딩하여 활용하는 방법을 보여줍니다.
class Thing extends Object implements Cloneable {
public String id;
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public boolean equals(Object obj) {
boolean result = false;
if ((obj != null) && obj instanceof Thing) {
Thing t = (Thing) obj;
if (id.equals(t.id)) result = true;
}
return result;
}
public int hashCode() {
return id.hashCode();
}
public String toString() {
return "This is: " + id;
}
}
public class Test {
public static void main(String args[]) throws Exception {
Thing t1 = new Thing(), t2;
t1.id = "Raj";
t2 = t1; // t1 == t2 이며 t1.equals(t2)
t2 = (Thing) t1.clone(); // t2 != t1 이지만 t1.equals(t2)
t2.id = "Adithya"; // t2 != t1 이며 !t1.equals(t2)
Object obj = t2;
System.out.println(obj); // toString()이 자동 호출됨
}
}
실행 결과
This is: Adithya
위 예제에서 System.out.println(obj)는 변수 obj를 Object 타입으로 참조하고 있지만, 실제 객체의 toString() 메서드가 런타임에 호출되어 "This is: Adithya"가 출력됩니다. 이것이 바로 Object 클래스를 통한 런타임 다형성의 대표적인 활용 사례입니다.