Java

[Java] Object 클래스

림 림 2021. 8. 8. 18:02
반응형

자바를 배울 때 모든 클래스는 Object 클래스를 상속받는 하위 클래스라고 배웠습니다. Object 클래스에 대해 더 자세히 공부해보았습니다.

Index

  1. Object 클래스의 전체 코드
  2. package
  3. import
  4. constructor
  5. getClass()
  6. hashCode()
  7. equals()
  8. clone()
  9. toString()
  10. notify(), notifyAll()
  11. wait()

 

1. Object 클래스의 전체 코드

Java 11의 Object 클래스의 코드를 보면 다음과 같습니다.

package java.lang;

import jdk.internal.HotSpotIntrinsicCandidate;

public class Object {

  private static native void registerNatives();
  static {
      registerNatives();
  }

  @HotSpotIntrinsicCandidate
  public Object() {}

  @HotSpotIntrinsicCandidate
  public final native Class<?> getClass();

  @HotSpotIntrinsicCandidate
  public native int hashCode();

  public boolean equals(Object obj) {
      return (this == obj);
  }

  @HotSpotIntrinsicCandidate
  protected native Object clone() throws CloneNotSupportedException;

  public String toString() {
      return getClass().getName() + "@" + Integer.toHexString(hashCode());
  }

  @HotSpotIntrinsicCandidate
  public final native void notify();


  @HotSpotIntrinsicCandidate
  public final native void notifyAll();

  public final void wait() throws InterruptedException {
        wait(0L);
  }

  public final native void wait(long timeoutMillis) throws InterruptedException;

  public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
    if (timeoutMillis < 0) {
    	throw new IllegalArgumentException("timeoutMillis value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
    	throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos > 0) {
    	timeoutMillis++;
    }
    
    wait(timeoutMillis);
  }


  @Deprecated(since="9")
  protected void finalize() throws Throwable { }
}

 

2. package

package java.lang;

Object 클래스는 java.lang 패키지에 위치하고 있습니다.

 

3. import

import jdk.internal.HotSpotIntrinsicCandidate;

jdk.internal 패키지에 있는 HotSpotIntrinsicCandidate 어노테이션을 import하고 있습니다.

Object 클래스 내부 코드를 보면, 멤버 변수가 없고 몇몇 멤버 메소드에 @HotSpotIntrinsicCandidate 어노테이션이 붙어있는 것을 볼 수 있습니다.

@HotSpotIntrinsicCandidate 어노테이션이 붙은 메소드는 HotSpot VM에 내재화(intrinsify)될 수도 있고 아닐 수도 있다는 것을 의미합니다.

HotSpot은 Java 11에서 사용하는 JVM 구현체입니다. 아래의 링크에서 더 자세한 내용을 확인할 수 있습니다.

Java Virtual Machine Guide

 

Java Virtual Machine Guide

 

docs.oracle.com

VM에 내재화된다는 것은 성능 향상을 위해 컴파일러가 메소드를 인라인 코드로 대체한다는 것입니다.

 

4. constructor

  @HotSpotIntrinsicCandidate
  public Object() {}

Object 클래스에는 기본 생성자가 하나 있습니다. Object 클래스는 멤버 변수가 없기 때문에 생성자의 매개변수도 없고 바디부분에서 처리하는 코드도 없습니다.

 

5. getClass()

  @HotSpotIntrinsicCandidate
  public final native Class<?> getClass();

getClass() 메소드는 Class 타입의 객체를 리턴합니다. 리턴된 Class 타입의 객체는 getClass()를 호출한 객체가 런타임에서 어떤 클래스의 객체인지를 표현하는 객체입니다.

 

6. hashCode()

  @HotSpotIntrinsicCandidate
  public native int hashCode();

hashCode() 메소드는 객체의 고유한 해시 코드 값을 리턴합니다. 해시 코드 값은 주로 객체의 메모리 주소에 해시 함수를 적용한 값입니다.(항상 그런 것은 아닙니다.)

hashCode() 메소드는 java.util 패키지의 HashMap이나 HashSet과 같이 해시 테이블을 구현하는 데에 사용됩니다.

 

7. equals()

  public boolean equals(Object obj) {
      return (this == obj);
  }

equals() 메소드는 이 메소드를 호출하는 객체와 매개변수로 전달되는 객체가 같은 객체인지 확인하는 객체입니다. == 연산자로 객체를 비교하기 때문에 두 객체가 참조하는 것이 같을 경우에 true를 리턴합니다.

여기서 잠깐,

hashCode()와 equals() 는 특별한 관계를 가집니다. 보편적으로 다음과 같이 사용됩니다.

  • equals() 메소드로 객체를 비교하는 데에 사용된 정보가 변경되지 않았다면(오버라이딩 하지 않았다면), 한번의 실행에서 hashCode() 메소드는 일관적으로 같은 해시 코드 값을 반환해야 한다.(다른 실행에서는 값이 달라질 수도 있다.)
  • equals() 메소드로 비교했을 때 같다면 hashCode() 메소드로 같은 해시 코드 값을 반환해야 한다.
  • equals() 메소드로 비교했을 때 다르다면 hashCode() 꼭 메소드로 다른 해시 코드 값을 반환해야하는 것은 아니다. 그치만 해시 테이블에서 사용할 경우에는 다른 해시 코드 값을 반환해야 해시 값이 충돌하는 일이 적어지기 때문에 성능을 향상시킬 수 있다.

따라서 equals() 메소드를 객체가 물리적으로 같은 메모리에 있는 값을 참조하는지 비교하기 위해 사용하는 것이 아니라 객체의 값이 논리적으로 같은지 사용하기 위해 오버라이딩했다면, hashCode() 메소드도 적절하게 오버라이딩하는 것이 좋습니다.

 

8. clone()

  @HotSpotIntrinsicCandidate
  protected native Object clone() throws CloneNotSupportedException;

clone() 메소드는 객체의 복사본 객체를 생성하고 반환합니다. 여기서 복사된 객체는 '같은 클래스 타입인 객체'를 말합니다. clone() 메소드를 호출하는 객체의 클래스는 Cloneable 인터페이스를 구현해야 합니다. 클래스가 Cloneable 인터페이스를 구현하지 않으면 CloneNotSupportedException 예외가 발생합니다.

  • x.clone() != x

위 식은 true입니다.

  • x.clone().getClass() == x.getClass()

위 식은 true일 수도 있고 false일 수도 있습니다.

관습적으로 clone() 메소드는 객체의 슈퍼 클래스의 clone()을 호출하여 얻어진 객체를 반환합니다. 클래스와 그 슈퍼클래스(Object 클래스 제외)가 이 컨벤션을 따른다면 이 식이 true가 됩니다.

  • x.clone().equals(x)

위 식은 true일 수도 있고 false일 수도 있습니다.

관습적으로 clone() 메소드를 통해 반환된 클래스는 clone() 메소드를 호출한 객체와 독립적입니다. 객체와 복사된 객체가 독립성을 유지하려면 객체의 슈퍼 클래스의 clone()을 호출하여 얻어진 객체를 리턴하기 전에 하나 이상의 필드가 수정되어야 합니다. 전형적으로 가변 객체는 복제되고 그에 대한 참조를 복사된 객체의 참조로 대체합니다. 만약 클래스의 필드가 모두 기본 자료형이고 불변 객체만을 참조한다면, 슈퍼 클래스의 clone() 메소드로 얻어진 객체의 필드를 수정하지 않아도 됩니다.

 

9. toString()

  public String toString() {
      return getClass().getName() + "@" + Integer.toHexString(hashCode());
  }

toString() 메소드는 객체를 표현하는 문자열을 리턴합니다. 문자열은 사람이 읽기 쉽고 간결해야 합니다.

Object 클래스의 toString() 메소드는 클래스의 이름과 '@'문자와 16진수로 표현한 객체의 해시 코드를 붙인 문자열을 반환합니다. 그래서 Object 클래스의 하위 클래스는 toString() 메소드를 적절하게 오버라이딩해서 활용하는 것을 추천합니다.

 

10. notify(), notifyAll()

  @HotSpotIntrinsicCandidate
  public final native void notify();

notify() 메소드는 객체 모니터에서 대기 상태에 있는 스레드 하나를 깨웁니다.

모든 객체들은 객체 모니터를 가지는데, 객체 모니터는 객체에 여러 스레드들이 동시에 접근하는 것을 막아주는 역할을 합니다. 객체 모니터에는 객체를 사용하려고 하는 스레드들이 대기하고 있고, 이 스레드들은 뒤에 나올 wait()메소드에 의해 대기 상태로 들어간 것입니다. 객체 모니터를 가지고있는 스레드는 이 객체에 대한 lock을 걸 수 있습니다.

notify()에 의해 깨어난 스레드는 현재 이 객체 모니터를 가지고 있는 스레드가 객체에 대한 lock을 풀 때 까지 기다려야 합니다. notify() 메소드는 이 객체 모니터를 가지고 있는 스레드에서만 호출되어야 합니다. 만약 현재 스레드가 객체 모니터의 소유자가 아니라면 IllegalMonitorStateException 예외가 발생합니다.

스레드가 객체 모니터의 소유자가 되는 방법은 다음 세 가지가 있습니다. 동시에 하나의 스레드만 객체 모니터를 소유할 수 있습니다.

  1. 객체의 동기화된 인스턴스 메소드를 실행하기
  2. 객체의 synchronized 문 안의 body를 실행하기
  3. Class 타입의 객체에서 synchronized static 메소드를 실행하기

 

  @HotSpotIntrinsicCandidate
  public final native void notifyAll();

notifyAll() 메소드는 객체 모니터에서 대기 상태에 있는 모든 스레드를 깨웁니다.

 

11. wait()

  public final void wait() throws InterruptedException {
        wait(0L);
  }

wait() 메소드는 현재 스레드가 notify() 메소드에 의해 깨워질 때까지 대기 상태로 만듭니다.

wait() 메소드도 객체 모니터의 소유자인 스레드에서 호출되어야 합니다. 만약 아니라면 IllegalMonitorStateException 예외가 발생합니다.

 

    public final native void wait(long timeoutMillis) throws InterruptedException;

wait(long timeoutMills) 메소드는 일정 시간이 경과할 때까지 스레드를 대기 상태로 만듭니다. 만약 timeoutMills가 0이라면 스레드가 깨워질 때까지 대기 상태로 만듭니다.

timeoutMills가 음수면 IllegalArgumentException 예외가 발생합니다.

스레드가 객체 모니터의 소유자가 아니면 IllegalMonitorStateException 예외가 발생합니다.

만약 현재 스레드가 대기 상태로 들어가기 전이거나 대기 상태인데 다른 스레드에 의해 인터럽트되어 중단된 경우InterruptedException 예외가 발생합니다.

 

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
      if (timeoutMillis < 0) {
          throw new IllegalArgumentException("timeoutMillis value is negative");
      }

      if (nanos < 0 || nanos > 999999) {
          throw new IllegalArgumentException(
                              "nanosecond timeout value out of range");
      }

      if (nanos > 0) {
          timeoutMillis++;
      }

      wait(timeoutMillis);
    }

wait(long timeoutMills, int nanos) 메소드는 일정 시간이 경과할 때까지 현재 스레드를 대기 상태로 만듭니다. 이 대기 상태에서는 객체에 대한 모든 동기화된 요청을 처리할 수 없습니다. wait 메소드를 호출한 객체의 lock만 풀고 대기 상태에 들어가며, 다른 객체에 대한 lock은 풀리지 않습니다.


12. finalize()

    @Deprecated(since="9")
    protected void finalize() throws Throwable { }

이 메소드는 Java 9부터 deprecated되어 사용되지 않는다. 이 객체에 대한 참조가 없을 경우에 가비지 컬렉터에서 호출되는 메소드입니다.

반응형

'Java' 카테고리의 다른 글

[Java] Reflection  (0) 2021.09.05
[Java] 동일성(Identity)와 동등성(Equality), 그리고 hashCode와 equals  (1) 2021.08.24
[Java] Oracle Java API 버그 제보하기  (0) 2021.08.08
[Java] JPMS와 Module  (0) 2021.08.08
[Java] JVM 구조  (0) 2020.10.25