Item 12. toString을 항상 재정의하라

Posted by yunki kim on May 8, 2022

  Object의 기본 toString() 메서드는 다음과 같다.

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

  따라서 기본 toString() 메서드를 그냥 사용한다면 유의미한 정보를 얻기 어렵다. 따라서 모든 하위 클래스에서 toString을 재정의 해야 한다.

  toString() 메서드를 직접 호출할 일은 드물다. 하지만, 객체를 println, printf, 문자열 연결 연산자(+), assert 구문에 넘길 때, 디버거가 객체를 출력할 때 자동으로 불린다. 또 한, 에러 로깅에도 사용할 수 있다. 따라서 toString()을 재정의 해야 코드의 문제를 파악하기 쉬워진다.

  실전에서 toString() 메서드는 그 객체가 가진 주요 정보를 모두 반환하는 것이 좋다(스스로를 완벽히 설명해야 한다). 만약 객체가 거대하거나 객체의 상태가 문자열로 표현하기 적합하지 않다면 "전화번호부(총 12345개)" 처럼 요약 정보를 반환해야 한다. 주요 정보가 다 담기지 않았다면, 다음과 같이 에러의 이유를 파악할 수 없다.

1
Asserttion failure: expected {abc, 123}, but was {abc, 123}
cs

  toString을 구현할 떄, 반환값의 포맷을 문서화할지 정해야 한다. 값 클래스라면 문서화하는 것이 좋다. 포맷을 정하면 그 객체는 표준적이고, 명확하고, 사람이 읽을 수있게 된다. 포맷을 명시한다면, 명시한 포맷에 맞는 문자열과 객체를 상호 전환하는 컨버터를 제공하면 좋다.

  포맷 문서화의 단점은 그 포맷에만 얽매이게 된다는 것이다. 만약 향후에 포맷을 바꾼다며, 기존 코드와 데이터는 엉망이 된다. 반대로 명시하지 않는다면 향후 릴리스에서 정보를 더 넣거나 포맷을 개선할 수 있는 유연성을 얻는다.

  포맷 명시 여부와 관련 없이 의도를 명확히 밝혀라.

  포맷 명시 여부와 관련 없이 toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공해야 한다. 그렇지 않으면 정보가 필요한 프로그래머는 toString의 반환값을 파싱해야 한다. 이는 성능에 영향을 미치고, 필요하지도 않은 작업이다.

  정적 유틸리티 클래스, 열거 타입은 toString을 재정의할 필요가 없다. 열거 타입은 대부분 완벽한 toString을 제공한다. 하지만 하위 클래스들이 공유해야할 문자열 표현이 있는 추상 클래스라면 toString을 재정의해줘야 한다.

  예를 들어 ArrayList의 경우 AbstractCollection에 있는 toString을 상속해 사용한다.

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
32
33
34
35
// AbstractList 상속
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    ...
}
 
// AbstractList는 AbstractCollection 상속
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    ...
}
 
// AbstractCollection의 toString 메서드
public abstract class AbstractCollection<E> implements Collection<E> {
    ...
 
    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }
    ...
}
 
 
 
 
cs

 

출처 - 이펙티브 자바