Java

[Effective Java] 3. 모든 객체의 공통 메서드

noahkim_ 2024. 12. 27. 17:15

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.


1. equals()는 일반 규악을 지켜 재정의하라

  • 정의하지 않으면 참조 주소값만 가지고 동치여부를 따짐

 

재정의할 필요 없는 경우

싱글턴 인스턴스일 경우
상위 클래스에서 재정의한 equals()가 하위 클래스에도 동일
  • AbstractSet - Set
  • AbstractList - List

 

재정의할 필요 있는 경우

상위 클래스가 equals()를 정의하지 않았을 경우
  • 값 클래스
    • Map의 key
    • Set의 원소

 

2. hashCode()도 재정의해라

  • equals()만 재정의할 경우, hash값으로 동치성을 판별하는 컬렉션 사용 시 결과가 이상하게 나옴

 

hashCode() 규약

  • hashCode()값을 생성할 때, 반드시 핵심 필드가 포함되어야 함
  • hashCode()를 여러번 호출해도 일관된 값을 리턴해야 함
  • equals()가 같다 판단했을 경우, hashCode()값도 같아야 함
  • equals()가 다르다 판단했을 경우, hashCode()값이 같을 수도 있음

 

3. toString()는 항상 재정의하라

  • 간결하면서 유익한 정보를 반환해야 함

 

기본값
  • 클래스_이름@해시코드(16진수)

 

4. clone()는 주의해서 재정의하라

Cloneable

clone()
  • Object의 protected 메서드
  • 필드들을 하나하나 복사한 객체를 반환
    • 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변을 보장해야 함
  • 동기화 필요

 

Stack

public class Stack implements Cloneable {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    @Override
    public Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
배열 복제 시, clone()이 권장됨
  • 타입 안정성 지원
    • 런타임 타입과 컴파일 타임 타입 모두가 원본 배열과 똑같은 배열을 반환
  • System.arraycopy()는 원본 배열의 타입을 보장하지 않음
    • 선언된 타입으로 복사됨

 

elements 필드는 final로 선언되면 안됨
  • 값을 할당할 수 없으므로 복제 불가

 

HashTable

public class HashTable implements Cloneable {
    private Entry[] buckets = ...;

    private static class Entry {
        final Object key;
        Object value;
        Entry next;
			
        Entry(Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
        
        // 이 Entry 객체가 첫 원소인 연결 리스트를 순환문으로 복사
        Entry deepCopy() {
            Entry result = new Entry(key, value, next);

            for (Entry p = result; p.next != null; p = p.next) {
                p.next = new Entry(p.next.key, p.next.value, p.next.next);
            }                

            return result;
        }
    }
    
    @Override 
    public HashTable clone() {
        try {
            HashTable result = (HashTable) super.clone();
            result.buckets = new Entry[buckets.length];
            
            for (int i = 0; i < buckets.length; i++) {
            	if (buckets[i] != null) { 
                    result.buckets[i] = buckets[i].deepCopy(); 
                }
            }                
                            
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }    
}
hashtable은 bucket의 배열
  • bucket을 clone할 경우, 원본과 같은 연결리스트를 참조함
    • 원본과 복제본 모두 예기치 않게 동작할 수 있음
  • 각 버킷을 구성하는 연결 리스트를 복사해야 함
    • HashTable의 Entry는 깊은 복사를 정의함

 

5. Comparable을 구현할 지 고려하라

compareTo()

static Comparator<Object> hashCodeOrder = new Comparator<>() {
    public int compare(Object o1, Object o2) {
        return Integer.compare(o1.hashCode(), o2.hashCode());
    }
}
  • 배열 혹은 리스트에서 원소들을 정렬할 때 기준을 정의하는 메서드
  • 값이 작을 수록 먼저 나옴