[JAVA] Object Class
모든 자바 클래스의 부모는 java.lang.Object 클래스다. 아무런 상속을 받지 않으면, Object 클래스를 확장하게된다.
Object 클래스에 선언되어 있는 메소드는 두 가지로 분류될 수 있다.
- 객체를 처리하기 위한 메소드
equals(Object obj)
,hashCode()
,toString()
,getClass()
,clone()
,finalize()
- 쓰레드를 위한 메소드
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(); // 모든 대기 중인 스레드 깨움
}
}