toString을 항상 재정의 하자
Object의 기본 toString 메서드는 우리가 작성한 클래스에 적합한 문자열을 반환 하는 경우는 없다.
PhoneNumber 클래스 toString 호출
PhoneNumber myHone = new PhoneNumber(02, 444, 7777);
System.out.println(myHone);
위에 코드를 실행하면 PhoneNumber@23fc625e
이 출력이 된다. 클래스_이름@16진수로_표시한_해시코드가 반환되는 것이다.
toString의 일반 규약에 따르면 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 반환해야 한다.
toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅 하기 쉽다. toString 메서드는 객체를 println, printf, 문자열 연결 연산자(+), assert 구문에 넘길 때 혹흔 디버거가 객체를 출력할 때 자동으로 불린다. 우리가 직접 호출하지 않더라도 다른 어딘가에서 쓰일 거란 이야기다. 예컨대 우리가 작성한 객체를 참조하는 컴포넌트가 오류 메세지를 로깅할 때 자동으로 호출할 수 있다. toString을 제대로 재정의하지 않는다면 쓸모없는 메세지만 로그에 남을 것이다.
toString 구현
toString을 구현할 때 반환값의 포맷을 문서화할지 정해야 한다. 전화번호나 행렬 같은 값 클래스라면 문서화하기를 권한다. 포맷을 명시하면 그 객체는 표준적이고 명확하고, 사람이 읽을 수 있게 된다. 따라서 그 값 그대로 입출력에 사용하거나 CSV 파일 처럼 사람이 읽을 수 있는 데이터 객체로 저장할 수도 있다. 포맷을 명시하기로 했다면, 명시한 포맷에 맞는 문자열과 객체를 상호 전환할 수 있는 정적 팩터리나 생성자를 함께 제공해주면 좋다. 자바 플랫폼의 많은 값 클래스가 따르는 방식이기도 하다. BigInteger, BigDecimal과 대부분의 기본 타입 클래스가 여기 해당한다.
단점도 있다. 포맷을 한번 명시하면 평생 그 포맷에 얽히게 된다. 이를 사용하는 프로그래머들이 그 포맷에 맞춰 파싱하고, 새로운 객체를 만들고, 영속 데이터로 저장하는 코드를 작성할 것이다. 만약 향후 릴리스에서 포맷을 바꾼다면 이를 사용하던 코드들과 데이터들은 엉망이 될 것이고, 프로그래머들은 절규할 것이다. 반대로 포맷을 명시하지 않는다면 향후 릴리스에서 정보를 더 넣거나 포맷을 개선할 수 있는 유연성을 얻게 된다.
포맷을 명시하든 아니든 의도를 명확히 밝혀야 한다.
/**
* 이 전화번호를 문자열 표현을 반환한다.
* 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다.
* XXX는 지역코드, YYY는 프리픽스, ZZZZ는 가입자 번호다.
* 각각의 대문자는 10진수 숫자 하나를 나타낸다.
*
* 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면,
* 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면
* 전화번호의 마지막 네 문자는 "0123"이 된다.
*/
@Override
public String toString() {
return String.format("%03d-%03d-%04d",
areaCode,prefix,lineNum);
}
포맷을 명시하지 않기로 했다면 다음처럼 작성할 수 있다.
/**
* 이 약물에 관한 대략적인 설명을 반환한다.
* 다음은 이 설명의 일반적인 형태이나,
* 상세 형식은 정해지지 않았으며 향후 변경될 수 있다.
* 각각의 대문자는 10진수 숫자 하나를 나타낸다.
*
* "[약물 #9 : 유형=사랑, 냄새=테레빈유, 겉모습=먹물]"
*/
@Override
public String toString() {...}
이펙티브자바 저자는, 포맷 명시 여부와 상관없이 toString이 반환한 값에 포함된 값에 포함된 정보를 얻어올 수 있는 API를 제공하자. 예컨대 PhoneNumber 클래스는 지역 코드, 프리픽스, 가입자 번호용 접근자를 제공해야 한다 라고 나와 있다.
public class PhoneNumber {
private int areaCode;
private int prefix;
private int lineNum;
//toString() 때문에 접근자를 제공해줘야 할까?
public int getAreaCode {return this.areadCode;}
public int getPrefix {return this.prefix;}
public int getLineNum {return this.lineNum;}
}
나는 이 말에 반대를 하고 싶다. 예를 들어, 지역코드는 PhoneNumber 클래스 내에서만 필요하므로, 외부에서 이 데이터에 접근할 필요가 없어서, getter를 제공해주지 않았는데, toString을 오버라이드 할 때, 이 데이터도 함께 toString의 반환값에 포함되었다는 이유로 getter를 만들어 줘야 하나? 아니면 애초에 toString()을 만들 때, 반환 값에서 제외를 했었어야 했나? 라는 역질문을 할 수 있다.
개인적으로는 로그기록이나, 디버깅 때문에 toString에는 모든 필드를 제공해야 한다라고는 생각한다. 그러나 객체지향 적인 관점에서 봤을 때 toString에서 해당 필드를 사용한다고 해서 꼭 getter나 파싱할 수 있는 API를 제공해줘야 하는 것은 필수가 아니라고 생각한다.