자바

[JAVA] Object Class

planting grass 2025. 3. 5. 10:12
728x90

모든 자바 클래스의 부모는 java.lang.Object 클래스다. 아무런 상속을 받지 않으면, Object 클래스를 확장하게된다.

Object 클래스에 선언되어 있는 메소드는 두 가지로 분류될 수 있다.

  1. 객체를 처리하기 위한 메소드
    • equals(Object obj), hashCode(), toString(), getClass(), clone(), finalize()
  2. 쓰레드를 위한 메소드
    • wait(), wait(long timeout), wait(long timeout, int nanos), notify(), notifyAll()
메서드 역할 재정의 가능 여부
equals(Object obj) 두 객체의 논리적 동등성 비교
hashCode() 객체의 해시코드 반환
toString() 객체의 문자열 표현 반환
getClass() 객체의 클래스 정보 반환
clone() 객체 복제 (얕은 복사)
finalize() 객체 소멸 시 호출 (비추천)
wait() 현재 스레드를 대기 상태로 변경
wait(long timeout) 특정 시간 동안 대기
wait(long timeout, int nanos) 특정 시간(나노초 포함) 동안 대기
notify() 대기 중인 스레드 하나를 깨움
notifyAll() 대기 중인 모든 스레드를 깨움

이제 하나하나 어떤 역할을 하는지 알아보자.

equals(Object obj)

객체가 동등한지 비교해준다.

  • 매개 타입: Object → 모든 객체가 매개값으로 사용 가능 (자동 타입 변환)
  • 리턴 타입: boolean → 두 객체가 같은 객체이면 true, 아니면 false

equals() 메서드는 기본적으로 "==" 연산자와 동일한 결과를 리턴하지만, 하위 클래스에서 논리적 동등 비교를 위해 재정의(오버라이딩)할 수 있다.

예시: String 클래스의 equals()
문자열 값이 같으면 true를 반환 (==이 아닌 값 자체를 비교)

기본 사용 예시

Object obj1 = new Object();
Object obj2 = new Object();

System.out.println(obj1.equals(obj2)); // false (서로 다른 객체)
System.out.println(obj1 == obj2);      // false (주소값 비교)

equals() 오버라이딩 예시

public class Member {
    private String id;

    public Member(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Member) { // obj가 Member 타입인지 확인
            Member member = (Member) obj;
            return this.id.equals(member.id); // id가 같으면 true
        }
        return false;
    }
}
public class Main {
    public static void main(String[] args) {
        Member m1 = new Member("user1");
        Member m2 = new Member("user1");
        Member m3 = new Member("user2");

        System.out.println(m1.equals(m2)); // true (id 같음)
        System.out.println(m1.equals(m3)); // false (id 다름)
    }
}

hashCode()

객체를 식별하는 정수값을 반환한다.

기본적으로 객체의 메모리 번지를 이용하여 해시코드 생성 → 객체마다 다른 값

HashMap, HashSet등의 컬렉션에서 equals()와 함께 동등성 비교에 사용된다.

hashCode()를 오버라이딩하여 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.

기본 사용 예시

Object obj1 = new Object();
Object obj2 = new Object();

System.out.println(obj1.hashCode()); // 예: 12345678
System.out.println(obj2.hashCode()); // 예: 87654321 (서로 다름)

hashCode() 오버라이딩 예시

public class Member {
    private String id;

    public Member(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Member) {
            Member member = (Member) obj;
            return this.id.equals(member.id);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return id.hashCode(); // id 값이 같으면 동일한 해시코드 반환
    }
}
public class Main {
    public static void main(String[] args) {
        Member m1 = new Member("user1");
        Member m2 = new Member("user1");

        System.out.println(m1.hashCode()); // 같은 해시코드
        System.out.println(m2.hashCode()); // 같은 해시코드
    }
}

toString()

기본적으로 "클래스명@해시코드" 형식의 문자열을 반환

하위 클래스에서 재정의하여 객체의 주요 정보 출력 가능

System.out.println(객체) 호출 시 자동으로 toString() 실행됨

기본 사용 예시

Object obj = new Object();
System.out.println(obj.toString()); // java.lang.Object@7f31245a

toString() 오버라이딩 예시

public class SmartPhone {
    private String company;
    private String os;

    public SmartPhone(String company, String os) {
        this.company = company;
        this.os = os;
    }

    @Override
    public String toString() {
        return company + ", " + os;
    }
}

public class Main {
    public static void main(String[] args) {
        SmartPhone phone = new SmartPhone("Samsung", "Android");
        System.out.println(phone); // Samsung, Android
    }
}

getClass()

객체의 클래스 정보를 Class<?> 타입으로 반환.

객체가 어느 클래스의 인스턴스인지 확인할 수 있음.

기본 예시

public class Example {
    public static void main(String[] args) {
        Example obj = new Example();
        System.out.println(obj.getClass().getName()); // Example
    }
}

clone()

기존 객체를 복제하여 새로운 객체 생성 (얕은 복사).

Object의 기본 clone()은 Cloneable 인터페이스를 구현한 클래스에서만 사용 가능.

기본 예시

public class Member implements Cloneable {
    String id;

    public Member(String id) {
        this.id = id;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Member original = new Member("user1");
        Member copy = (Member) original.clone();
        System.out.println(copy.id); // "user1"
    }
}

finalize()

객체가 가비지 컬렉션(GC) 에 의해 제거되기 직전에 호출된다.

자원의 정리(Cleanup) 를 위한 용도로 사용된다. (예: 파일 닫기, 네트워크 연결 해제).

Java 9부터 Deprecated(비추천) → 가비지 컬렉션의 실행 시점을 보장할 수 없기 때문이다.

대신 try-with-resources 또는 AutoCloseable을 사용하는 것이 좋다.

기본 예시

public class Example {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("객체가 가비지 컬렉션에 의해 제거됨");
    }
}

public class Main {
    public static void main(String[] args) {
        Example obj = new Example();
        obj = null; // 객체를 null로 만들어 GC 대상이 됨
        System.gc(); // 강제 가비지 컬렉션 요청 (실제로 실행될지는 JVM 결정)
    }
}

실행하면 객체가 가비지 컬렉션에 의해 제거됨

!하지만, GC 실행 여부는 JVM이 결정하므로 항상 호출된다는 보장이 없다.

대신 AutoCloseable을 사용해서 명시적으로 리소스를 해제하는 것이 더 안전하다.

wait()

wait() 메서드는 현재 실행 중인 스레드를 대기 상태로 만들고, notify() 또는 notifyAll()이 호출될 때까지 기다린다.

멀티스레드 환경에서 동기화(Synchronization)를 위해 사용된다.

synchronized 블록 안에서만 호출이 가능하다.

wait()이 호출된 스레드는 notify()가 호출될 때까지 기다린다.

기본 사용 예시

class SharedObject {
    public synchronized void waitMethod() throws InterruptedException {
        System.out.println("스레드 대기 시작...");
        wait(); // 현재 스레드를 대기 상태로 변경
        System.out.println("스레드 다시 실행됨!");
    }

    public synchronized void notifyMethod() {
        System.out.println("스레드 깨우기 실행!");
        notify(); // 대기 중인 스레드 하나를 깨움
    }
}
public class Main {
    public static void main(String[] args) {
        SharedObject obj = new SharedObject();

        Thread t1 = new Thread(() -> {
            try {
                obj.waitMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(obj::notifyMethod);

        t1.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) {} // t1이 먼저 실행되도록
        t2.start();
    }
}

실행 결과

스레드 대기 시작...
스레드 깨우기 실행!
스레드 다시 실행됨!

wait(long timeout)

특정 시간(ms) 동안 현재 스레드를 대기 상태로 변경한다.

지정된 시간이 지나면 자동으로 깨어난다.

wait()과 동일하지만, 주어진 시간이 지나면 자동으로 대기 상태가 해제된다.

기본 사용 예시

class SharedObject {
    public synchronized void waitMethod() throws InterruptedException {
        System.out.println("스레드 대기 시작 (2초간)...");
        wait(2000); // 2초 후 자동 해제
        System.out.println("스레드 다시 실행됨!");
    }
}
public class Main {
    public static void main(String[] args) {
        SharedObject obj = new SharedObject();

        Thread t1 = new Thread(() -> {
            try {
                obj.waitMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
    }
}

실행 결과

스레드 대기 시작 (2초간)...
(2초 후)
스레드 다시 실행됨!

wait(long timeout, int nanos)

wait(long timeout)과 동일하지만, 나노초(nanoseconds) 단위로 더 정밀한 대기 시간 설정 가능하다.

지정된 timeout(ms)과 nanos(나노초) 동안 대기 후 자동 해제된다.

기본 사용 예시

lass SharedObject {
    public synchronized void waitMethod() throws InterruptedException {
        System.out.println("스레드 대기 시작 (2초 + 500나노초)...");
        wait(2000, 500); // 2초 + 500나노초 대기
        System.out.println("스레드 다시 실행됨!");
    }
}

notify()

wait()으로 대기 중인 한 개의 스레드를 깨운다.

notify()를 호출하면 wait() 상태에서 대기 중인 임의의 하나의 스레드를 깨운다.

기본 사용 예시

class SharedObject {
    public synchronized void notifyMethod() {
        System.out.println("스레드 깨우기 실행!");
        notify(); // 대기 중인 스레드 하나 깨움
    }
}

notifyAll()

wait()으로 대기 중인 모든 스레드를 깨운다.

notify()는 하나만 깨우지만, notifyAll()은 모든 스레드를 깨운다.

기본 사용 예시

class SharedObject {
    public synchronized void notifyAllMethod() {
        System.out.println("모든 스레드 깨우기 실행!");
        notifyAll(); // 모든 대기 중인 스레드 깨움
    }
}
728x90