Posts [이펙티브자바] 아이템12-toString을 항상 재정의하라
Post
Cancel

[이펙티브자바] 아이템12-toString을 항상 재정의하라

toString의 일반 규약에 따르면서 ‘간결하면서 사람이 읽기 쉬운 형태의 유익한 정보’를 반환해야 한다.

  • Object의 toString을 재정의하지 않고 사용할 경우 아래와 같이 클래스명@16진수로 표시한 해시코드로 나타나게 된다.
  • 이는 해당 객체가 어떤 상태인지 어떤 정보를 담고 있는지를 전혀 알 수 없다.

스크린샷 2022-06-06 오후 4 01 58

  • 또한 toString의 규약은 “모든 하위 클래스에서 이 메서드를 재정의하라”고 한다.
  • toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅하기 쉽다.
    • 객체를 println, printf, 문자열 연결 연산자(+), assert 구문에 넘길 떄 혹은 디버거가 객체를 출력할 때 자동으로 불리기 때문이다.
  • 만약 PhoneNumber 용 toString을 제대로 재정의했다면 아래 코드만으로 문제를 진단하기에 충분한 메시지를 남길 수 있다.
    • 반대로 재정의를 하지 않았다면 그다지 쓸모 없는 메시지가 출력된다.
1
System.out.println(phoneNumber + "에 연결할 수 없습니다.");

좋은 toString은 이 인스턴스를 포함하는 객체에서 유용하게 쓰인다.

  • map 객체를 출력시 {Jenny=PhoneNumber@addbd} 보다는 {Jenny=707-867-5309} 라는 메시지가 나오는게 훨씬 map 의 상태를 파악하는데 도움이 될 것이다.

실전에선 toString은 그 객체가 가진 주요 정보 모두를 반환하는게 좋다.

  • 하지만 객체가 거대하거나 객체의 상태가 문자열로 표현하기에 적합하지 않다면 무리가 있다.
    • 맨허튼 거주자 전화번호부(총 1487536개)Thread[main,5,main] 같은 요약 정보를 담아야 한다.
    • 이상적으로는 스스로를 완벽히 설명하는 문자열이어야 한다.

toString을 구현할 때 반환값의 포맷을 문서화할지 정해야 한다.

  • 전화번호(PhoneNumber)와 같은 값 클래스라면 문서화하기를 권한다.
  • 포맷을 명시하면 그 객체는 표준적이고, 사람이 읽을 수 있게 된다. (자바의 많은 값 클래스가 따르는 방식이기도 하며 BigInteger, BigDecimal과 같은 대부분의 기본 타입 클래스가 여기에 해당한다.)
  • 단점도 있다. 포맷을 한 번 명시하면 평생 그 포맷에 얽매이게 된다.
    • 이를 사용하는 클라이언트들은 그 포맷에 맞춰 파싱하고, 새로운 객체를 만들고 할텐데 그러다보면 향후 릴리스에서 포맷을 바꿀 경우 이를 사용하던 코드들과 데이터들은 모두 엉망이 될 것 이다.
    • 반면 포맷을 명시하지 않는다면 향후 릴리스에서 포맷을 바꾸거나 정보를 더 넣는 등 유연성을 얻을 수 있다.

포맷을 명시하든 아니든 여러분의 의도는 명확히 밝혀야 한다.

  • 포맷을 명시하려면 아주 정확하게 해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
     * 이 전화번호의 문자열 표현을 반환한다.
     * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다.
     * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다.
     * 각각의 대문자는 10진수 숫자 하나를 나타낸다.
     *
     * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면,
     * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면
     * 전화번호의 마지막 네 문자는 "0123"이 된다.
     */
    @Override public String toString() {
        return String.format("%03d-%03d-%04d",
                areaCode, prefix, lineNum);
    }
  • 포맷을 명시하지 않기로 했다면 다음처럼 작성할 수 있을 것이다.
1
2
3
4
5
6
7
/**
     * 이 약물에 관한 대략적인 설명을 반환한다.
     * 다음은 이 설명의 일반적인 형태이나,
     * 상세 형식은 정해지지 않았으며 향후 변경될 수 있다.
     * "[약물 #9: 유형=사랑, 냄새=테레반유, 겉모습=먹물]"
     */
    @Override public String toString() { ... }
  • 이러한 설명을 읽고도 이 포맷에 맞춰 코딩하거나 특정 값을 빼내어 영구 저장한 프로그래머는 나중에 포맷 변경에 대한 피해를 입어도 자기 자신을 탓할 수 밖에 없을 것이다..

toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자.

  • 주관적인 판단으로 여기서 말하는 반환한 값에 포함된 정보를 얻어올 수 있는 API는 Getter로 해석되어 진다.
  • toString의 반환값으로 클라이언트가 파싱하여 사용하는 것을 방지하는 목적으로 설명하고 있다.

정적 유틸리티 클래스(아이템4)는 toString을 제공할 이유가 없다.

  • 실제 값 객체도 아니고 객체를 생성해서 사용하지 않기 때문일 것이다.

대부분의 열거 타입(아이템34)도 자바가 이미 완벽한 toString을 제공하니 따로 재정의하지 않아도 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum PhoneType {
    BASIC("CODE_BASIC"),
    PREMIUM("CODE_PREMIUM");

    private String code;

    private PhoneType(String code) {
        this.code = code;
    }

    public String getCode() {
        return this.code;
    }
}

스크린샷 2022-06-06 오후 4 22 43

Note: 직접 실습 코드를 작성해보니, code 값은 별도로 출력되지 않는듯하여 toString 재정의가 필요하다고 생각이 듭니다..

하지만 하위 클래스들이 공유해야 할 문자열 표현이 있는 추상클래스라면 toString을 재정의해줘야 한다.

  • 예컨대 대다수의 컬렉션 구현체는 추상 컬렉션들의 toString 메서드를 상속해 쓴다.

스크린샷 2022-06-06 오후 4 24 44

스크린샷 2022-06-06 오후 4 26 54

AutoValue 프레임워크는 toString 자동 생성해준다.

  • 대부분의 IDE도 마찬가지다.
  • AutoValue는 각 필드의 내용을 멋지게 나타내 주긴 하지만 클래스의 의미까진 파악하진 못한다,
    • 예컨대, PhoneNumber 클래스는 값 객체이기에 자동 생성에 적합하지 않다.
  • 그래도 객체의 값에 관해 아무것도 알려주지 않는 Object의 toString 보다는 자동 생성된 toString이 훨씬 유용하다.

핵심 정리: 모든 구체 클래스에서 Object의 toString을 재정의하자. 상위 클래스에서 이미 알맞게 재정의한 경우는 예외다. toString을 재정의한 클래스는 사용하기도 즐겁고 디버깅하기 쉽게 해준다. toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야 한다.

This post is licensed under CC BY 4.0 by the author.

[이펙티브자바] 아이템11-equals를 재정의하려거든 hashcode도 재정의하라

[이펙티브자바] 아이템13-clone 재정의는 주의해서 진행하라