티스토리 뷰

Object

Object 클래스는 모든 클래스의 최고 조상으로, 모든 인스턴스들이 가져야 할 기본적인 11개의 메서드만을 가지고 있다.

toString()

어떤 인스턴스에 대한 정보를 String으로 제공할 목적으로 정의된 메서드이다.

Object 클래스에서는 클래스이름@16진수 해시코드 를 반환하도록 구현되어 있다.

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

클래스를 만들면서 원하는 출력 방식이 있다면 toString() 메서드를 적절히 오버라이딩 해 주어야 한다.

그 방식으로는 대체로 인스턴스나 클래스에 대한 정보나 인스턴스 변수들의 값을 문자열로 반환하도록 오버라이딩 되는 것이 일반적이다.

equals()

paramete로 어떤 객체의 참조변수를 받아, 현재 객체와 비교하여 그 결과를 boolean type으로 반환하는 메서드이다.

그런데 Object class 에서 구현된 방식은 단순히 두 객체에 대한 참조변수의 비교이다.

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

즉 기본적인 구현 방식은 두 객체의 참조변수 값에 대한 비교 뿐이므로 서로 다른 두 객체를 equals 메서드를 통해 비교하면 반드시 false를 결과로 얻게 된다.

따라서 내가 원하는 방식으로 두 객체의 내용을 서로 비교하기 위해서는 equals() 메서드를 객체의 주소가 아닌 객체에 저장된 내용을 내가 원하는 방식으로 비교하도록 오버라이딩 해 주어야 한다.

equals() 메서드는 주로 이렇게 오버라이딩 된 후, 두 객체의 내용을 비교하기 위해서 사용되는 것이 일반적이다. String이나 Date, 그 밖의 기타 Wrapper 클래스 등의 equals 메서드들도 각 객체의 내용을 비교하도록 오버라이딩 되어 있다.

(하지만 StringBuffer 클래스에서는 오버라이딩 되어있지 않다.)

hashCode()

해당 객체의 해시코드를 리턴하는 함수로, 다음 조건을 만족한다.

  • Application을 실행시키는 동안에 같은 객체에 이 메서드를 사용 할 경우, 반드시 같은 결과값을 리턴해야 한다. 단, Application을 여러 번 실행시키는 동안에 계속 같은 해시코드 값을 가질 필요는 없다. 한 번의 실행 컨텍스트에서 같은 값을 가지면 된다.
  • 두 객체가 equals() 메서드를 통해 같은 객체라는 결과를 얻는다면, 두 객체는 반드시 같은 해시코드 값을 반환해야 한다.
  • 서로 다른 두 객체가 같은 해시코드 값을 리턴 할 수도 있다.
    • 하지만 Object에 정의된 hashCode() 메서드는 서로 다른 객체가 서로 다른 해시코드를 반환하도록 구현되어 있다고 한다.

위 조건을 고려 해 보면, 클래스에서 equals()를 오버라이딩 해 주었다면 hashCode() 메서드도 반드시 오버라이딩 해 주어 equals() 메서드의 연산 결과가 True인 객체들(내가 정의한 기준에 따라 서로 같은 객체들)이 서로 같은 해시코드 값을 갖도록 해야 한다.

만약 equals() 메서드를 오버라이딩 하고 hashCode() 메서드를 오버라이딩 하지 않을 경우 여러가지 문제가 발생할 수 있는데, 한가지 예시를 들자면 HashMap 사용시 발생할 수 있는 문제를 들 수 있을 것이다.

우선, HashMap 클래스의 메서드들 중 두 가지를 잠시 살펴보자.

// ... 전략
public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}
// ... 중략
public final boolean equals(Object o) {
    if (o == this)
        return true;
    if (o instanceof Map.Entry) {
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        if (Objects.equals(key, e.getKey()) &&
            Objects.equals(value, e.getValue()))
            return true;
    }
    return false;
}

먼저, 아래쪽의 equals 메서드를 살펴보면 파라미터로 들어온 객체의 equals 메서드를 다시 호출하여 비교를 수행한다. 즉, Map에서 모든 노드를 하나씩 하나씩 꺼내 보면서 equals 메서드를 사용하여 비교를 해 나간다면 내가 찾고있는 노드가 그 Map에 있는지 없는지 정상적으로 알 수 있을것이다.(효율은 매우 나쁘겠지만!)

그런데 만약 멤버십 테스트를 더 효율적으로 하기 위해 contains나 containsKey 메서드 등을 사용한다면 문제가 발생하게 된다.
위 코드에서 containsKey 메서드를 보면 파라미터로 넘겨받는 객체를 key로 사용하는 hash 함수를 사용하는 것을 볼 수 있다.
그리고 이 hash 함수의 내용은 다음과 같다.

static final int hash(Object key) {
   int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

위 코드에서 볼 수 있듯, key로 넘겨받은 객체의 hashCode 메서드를 호출한다. 이는 곧 hashCode 메서드를 오버라이딩 해 주지 않았다면 멤버십 테스트의 결과가 항상 false가 되어 정상적인 멤버십 테스트가 이루어질 수 없음을 의미한다. 운이 좋게도(?) 당장은 해시 함수를 어떠한 형태로든 사용하지 않아 hashCode 메서드를 오버라이딩 하지 않고도 문제가 발생하지 않을 수는 있겠지만, 이는 언제 터질지 모르는 불발탄을 안고 가는 것과 같으므로 항상 주의해서 이런 일이 없도록 해야겠다는 생각이 든다.

그렇다면 hashCode 메서드는 어떻게 구성되어 있고, 어떻게 오버라이딩 해 주어야 할까?라는 생각이 들게 되는데, 실제 코드를 살펴 보면 다음과 같이 나타나 있다.

@HotSpotIntrinsicCandidate
public native int hashCode();

여기서 native keyword에 대해서 찾아보면, 자바가 아닌 다른 언어로 구현된 코드를 자바에서 사용하려고 할 때 쓰는 키워드라고 한다. 즉, Java 코드로 된 소스코드를 찾을 수 없었다. 실제로 구현된 소스코드를 이리저리 한 번 찾아보았는데 좀처럼 찾을 수가 없었다. 이에 native keyword의 역할 및 Java Object 클래스의 hashCode 메서드의 동작에 대해서는 시간을 좀 넉넉히 잡고 천천히 조사하는 것으로 하고, 일단 여기서 한 번 끊기로 한다.

추가적으로, hashCode() 메서드와 관련해서 찾아보던 중 도움이 되는 글을 하나 찾을 수 있었다. 참고로 하면 좋을 것 같다.

그밖의 나머지 메서드들에 대해서도 간단하게 살펴보면,

clone()

  • 자신을 복제하여 새로운 인스턴스를 생성 및 반환하는 메서드로, 작업을 수행하면서 원래의 값을 보존할 필요가 있을 때 유용하다. (복원 또는 전후 값 비교 등)
  • 기본적으로 shallow copy가 이루어진다.
    • 때문에 reference type의 멤버변수를 가지고 있는 경우라면 deep copy가 이루어질 수 있도록 구현해야 한다.
    • shallow copy의 경우 reference type이 아닌 멤버변수 등의 field들은 정상적으로 복사가 되지만, reference type의 멤버변수라면 해당 주소값만 복사가 되기 때문에 기존의 객체가 가지고 있던 reference type의 멤버변수인 객체를 공유하게 되는 문제가 발생한다. 자세한 내용은 Object copying을 한번 쯤 읽어두면 좋을 것 같다.
  • clone 메서드의 사용을 위해서는 Clonable 인터페이스를 구현하고, clone() 메서드를 오버라이딩 하면서 접근 제어자를 public으로 변경 해 주어야 한다.
  • clone()으로 복제가 가능한 클래스인지 확인하려면 Java API 페이지에서 Clonable을 구현하였는지 확인하면 된다.

getClass()

  • 자신이 속한 클래스의 Class 객체를 반환하는 메서드로, Class 객체는 클래스의 모든 정보를 담고 있으며 클래스당 한 개만 존재하는 객체이다.

notify(), notifyAll(), wait()

객체 사용을 원하는 쓰레드에게 알리거나, 객체 사용을 위해 대기하도록 하는 메서드. 멀티쓰레드 프로그래밍을 할 때 좀 더 자세히 살펴보면 좋을 것 같다.

'Programmers 데브코스 > 더 알아보기' 카테고리의 다른 글

23 Design Patterns  (0) 2021.08.08
[Java] StringBuffer와 StringBuilder  (0) 2021.08.06
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함