JAVA/자바의 신

12장. 모든 클래스의 부모 클래스는 Object에요

hyunsb 2024. 1. 5. 20:41
💡 해당 글은 『자바의 신 3판』을 복습하며 도서의 내용과 본인의 주관적인 생각을 정리한 글입니다.

☁️ 내용정리

Object

자바에서 Object란 모든 클래스의 부모가 되는 클래스이다. java.lang 패키지에 선언되어 있다.

클래스 계층 구조의 root에 해당한다. 모든 클래스는 Object가 될 수 있다.

 

Object 클래스에는 객체의 기본적인 행동을 정의해놓았다.

Object에 존재하는 메서드는 객체를 처리하기 위한 메서드와 스레드를 위한 메서드로 나눌 수 있다.

  • protected Object clone(): 객체의 복사본을 만든다.
  • public boolean equals(Object obj): 매개변수와 this가 논리적으로 동등한지 판단한다.
  • protected void finalize(): gc에 의해 호출되는 객체 수거 메서드이다.
  • public class<?> getClass(): 객체의 클래스 객체를 반환한다.
  • public int hashCode(): 객체의 해시코드를 반환한다.
  • public String toString(): 객체를 문자열로 표현하여 반환한다.

 

==과 equals()

==은 피연산자의 값을 비교하기 위해 사용된다, 이는 피연산자들이 물리적으로 동일한지를 판단한다. 피연산자가 모두 reference type인 경우 주소값을 비교한다.

 

equals()는 피연산자들이 논리적으로 동등한지를 판단하기 위해 사용된다. 재정의 하지 않는다면 ==과 동일한 기능을 구행한다.

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

equals는 아래의 규칙에 맞추어 재정의해야 한다.

  • x.equals(x) == true
  • a.equals(b) == b.equals(a)
  • a.equals(a) == b.equals(c) == c.equals(a)
  • 멱등성을 가져야 함.
  • null에 대해선 항상 false

 

hashCode()

hashCode는 기본적으로 객체의 메모리 주소를 기반으로 한 16진수 int타입을 반환한다.

hashCode가 같다고 해서 같은 객체인 것은 아니다. 해시 충돌의 가능성이 있기 때문이다. 즉 hashCode가 같다면 equals도 같다 라는 것은 거짓이다. hashCode가 다르다면 무조건 다른 객체인 것은 참이다.

반대로 equals가 true라면 hashCode는 동일해야 한다. 이런 결과가 나오지 않게 재정의했다면 언젠간 어디서든 사이드이펙트가 발생할 것이다.

 


 

☁️ 내 생각

equals() 재정의 시, hashCode()도 재정의해야 하는 이유

hash를 사용하는 컬렉션에서 오동작할 수 있는 가능성을 제거하기 위해서라고 생각한다.

HashMap의 내부 코드를 살펴보면 아래와 같이 동작하게 되어있다.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;

    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);

    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else { // ...

put 동작 시 먼저 key의 해시코드 값을 비교한다. 해시 값이 같으면 equals를 사용하여 동등한지 판단한다.

즉, hashCode 반환값이 다르다면 equals 메서드를 호출하기도 전에 다른 객체로 인식한다는 의미이다.

key를 통해 element를 찾는 작업도 동일하게 이루어진다.

그 이후, <K, V>를 가지는 Node의 배열 table변수에 hashCode()값을 인덱스로 사용하여 저장한다.

 

그렇다면 hashCode()는 어떻게 재정의해야 할까?

 

 

좋은 hashCode()는 어떻게 재정의해야 할까

좋은 hashCode()는 사용자의 코드에서 논리적 동등의 기준이 되는 상태를 가지고 만들어야 할 것이고, 다른 상태를 가진 인스턴스에 대해 최대한 중복 값이 발생하지 않아야 할 것이다.

다른 상태를 가진 인스턴스에 대해 같은 값을 반환하는 경우, 해시 충돌을 체이닝 방식으로 해결하는 HashMap의 탐색속도가 O(n)까지 증가할 수 있기 때문이다.

좋은 hashCode()를 작성하는 방법은 추후 이펙티브자바 정리 포스팅에서 다뤄볼 생각이다.

 

 


 

☁️ 질문

  • toString()을 재정의하면 좋은 점
    • 객체의 상태를 문자열로 표현할 수 있는 방법을 미리 구현 해놓기 때문에 특정 시점의 객체의 상태를 로깅하는 데 유용함
  • == 과 equals()의 차이점
    • ==은 두 객체 혹은 값이 물리적으로 동일한지 판단하는 연산이고, equals()는 두 객체가 논리적으로 동등한지 판단하는 방식이다.
  • equals() 재정의 시, hashCode()도 재정의해야 하는 이유
    • hash를 사용하는 컬렉션에서 오동작할 수 있는 가능성을 제거하기 위해서.