
자바에서 모든 클래스의 최상위 부모는 Object 클래스다.
즉, 개발자가 어떤 클래스를 만들어도 암묵적으로 Object를 상속받는다.
이로 인해 모든 객체는 공통된 기본 기능을 가지며, 다형성의 기반을 공유한다.
Object 클래스는 java.lang 패키지에 속한다.
이 패키지는 자바 언어의 핵심적인 클래스들을 포함하며, 별도의 import 없이도 자동으로 사용할 수 있다.
| 클래스 | 설명 |
|---|---|
Object | 모든 클래스의 부모 |
String | 문자열 클래스 |
Integer, Long, Double | 기본형을 객체로 다루는 래퍼 클래스 |
Class | 클래스의 메타정보를 담는 클래스 |
System | 시스템 관련 기능 제공 |
Object는 모든 클래스의 부모로서 다음과 같은 두 가지 핵심 역할을 수행한다.
객체의 정보를 출력하거나 비교하는 기능은 대부분의 클래스에서 필요하다.
이 기능을 매번 직접 구현하기엔 비효율적이기 때문에, Object는 이러한 공통 기능을 미리 제공한다.
| 메서드 | 설명 |
|---|---|
toString() | 객체의 정보를 문자열 형태로 반환 |
equals() | 두 객체의 논리적 동등성 비교 |
getClass() | 객체의 클래스 정보 반환 |
모든 클래스는 Object를 상속받으므로 이 기능들을 기본적으로 사용할 수 있다.
Object는 모든 클래스의 조상이므로 어떤 객체든 Object 타입으로 참조할 수 있다.
이로 인해 서로 다른 타입의 객체를 하나의 메서드나 배열로 다룰 수 있다.
Object a = new String("hello");
Object b = new Integer(100);
Object c = new Dog();
단, Object 타입은 자식 클래스의 세부 기능을 모르기 때문에,
자식의 메서드를 사용하려면 다운캐스팅이 필요하다.
class Car {
void move() {
System.out.println("자동차 이동");
}
}
class Dog {
void sound() {
System.out.println("멍멍");
}
}
public class ObjectPolyExample {
public static void main(String[] args) {
action(new Dog());
action(new Car());
}
static void action(Object obj) {
if (obj instanceof Dog dog) {
dog.sound();
} else if (obj instanceof Car car) {
car.move();
}
}
}
실행 결과
멍멍
자동차 이동
Object를 사용하면 모든 객체를 전달받을 수 있지만,
실제 동작을 수행하려면 객체의 타입을 구분해 다운캐스팅해야 한다.
Object 배열은 모든 객체를 저장할 수 있는 범용 컨테이너다.
public class ObjectArrayExample {
public static void main(String[] args) {
Object[] arr = { new Dog(), new Car(), new Object() };
System.out.println("저장된 객체 수: " + arr.length);
}
}
실행 결과
저장된 객체 수: 3
이 배열은 어떤 클래스가 추가되더라도 그대로 사용할 수 있다.
즉, 수정 없이 확장이 가능한 구조를 제공한다.
Object.toString()은 객체의 정보를 문자열로 반환하는 메서드다.
Object obj = new Object();
System.out.println(obj.toString());
System.out.println(obj);
실행 결과
java.lang.Object@6d06d69c
java.lang.Object@6d06d69c
println()은 내부적으로 toString()을 호출하기 때문에
객체를 직접 출력해도 toString()의 반환값이 출력된다.
Object의 기본 toString()은 클래스 이름과 해시코드를 반환하지만,
보통은 객체의 상태를 보기 쉽게 표현하기 위해 오버라이딩한다.
public class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{name='" + name + "', age=" + age + "}";
}
}
System.out.println(new Dog("멍멍이", 3));
실행 결과
Dog{name='멍멍이', age=3}
equals()는 두 객체가 논리적으로 같은지를 판단하는 메서드다.
기본적으로 Object의 equals()는 ==과 동일하게 동일성(참조 비교)만 수행한다.
public class User {
private String id;
public User(String id) {
this.id = id;
}
}
User u1 = new User("A");
User u2 = new User("A");
System.out.println(u1 == u2); // false
System.out.println(u1.equals(u2)); // false
equals()를 재정의하면 논리적 동등성을 비교할 수 있다.
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return id.equals(user.id);
}
이제 id가 같으면 논리적으로 동일한 객체로 판단된다.
Object를 사용하면 서로 관련 없는 클래스도 공통된 방식으로 처리할 수 있다.
예를 들어, 모든 객체의 정보를 출력하는 다음 코드를 보자.
public class ObjectPrinter {
public static void print(Object obj) {
System.out.println("객체 정보: " + obj.toString());
}
}
ObjectPrinter는 Object와 toString()에만 의존한다.
새로운 클래스가 추가되더라도 print() 메서드를 수정할 필요가 없으며,
각 클래스는 toString()만 오버라이딩하면 된다.
이 구조는 OCP(개방-폐쇄 원칙)을 잘 지키는 예시다.
System.out.println() 역시 Object 타입을 매개변수로 받는다.
내부적으로 toString()을 호출해 객체의 정보를 출력한다.
이처럼 자바의 기본 기능조차도 Object의 공통 기능과 다형성을 기반으로 설계되어 있다.