이 아이템은 개발을 해본 사람이라면 너무나도 당연한 이야기이다.
Object의 기본 toString 메서드는 PhoneNumber@adbbd처럼 단순히 클래스_이름@16진수_해시코드를 반환할 뿐이다.
toString의 일반 규약에 따라 '간결하면서 사람이 읽기 쉬운 형태의 유익한 정보'를 반환하라.
또한 "모든 하위 클래스에서 이 메서드를 재정의하라"
toString을 잘 구현하는 것만으로도 디버깅에 굉장히 유용하다.
이는 심지어 직접 사용하는 경우를 제외하더라도 assert에 의한 자동 호출이나, println, 문자열 연결 연산자 등에서도 사용된다. (다른 어딘가에서는 쓰일 것이란 이야기이다.)
반면, toString을 재정의해주지 않는다면 로그에는 쓸 데 없는 정보만 알려주고 있을 것이고, 해당 인스턴스가 어떤 정보를 담고 있는지 유추가 힘들어진다.
toString을 제대로 정의했다면 다음 코드만으로 문제를 진단하기에 충분한 메시지를 남길 수 있다.
System.out.println(phoneNumber + "에 연결할 수 없습니다.");
생각해보자.
이렇게 출력을 했을 때, "PhoneNumber@해시코드에 연결할 수 없습니다."가 출력되는 것이 보기 좋은가, "010-1234-5678에 연결할 수 없습니다."가 보기 좋은가.
좋은 toString은 해당 객체가 가진 주요 정보를 보여주어야 한다.
- toString은 그 객체가 가진 주요 정보를 모두 반환하는 것이 좋다.
- 만약, 객체가 거대하거나 객체 상태가 문자열로 표현하기에 적합하지 않다면, 스스로를 완벽히 설명하는 요약 정보를 담아야 한다.
- 포맷을 명시하든 아니든 의도는 명확히 밝혀야한다.
- 반환값의 포맷을 한 번 명시하면 평생 그 포맷에 얽매이게 된다. 프로그래머들이 그 포맷에 맞추어 파싱하고, 새로운 객체를 만들고, 영속 데이터로 저장하는 코드를 작성해버렸다면, 다음 릴리즈에서 포맷을 변경할 시 프로그래머들은 모든 코드를 수동으로 고쳐야만 한다.
- 포맷을 명시하려면 아주 정확하게 해야한다. 그래야지 이 포맷에 맞춰 개발한 프로그래머가 자기 자신을 탓할 수밖에 없다.
// 포맷을 명시할 경우
/*
* 이 전화번호의 문자열 표현을 반환한다.
* "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다.
* XXX는 지역코드, YYY는 프리픽스, ZZZZ는 가입자 번호다.
* 각각의 대문자는 10진수 숫자 하나를 나타낸다.
*
* 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면,
* 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면
* 전화번호의 마지막 네 문자는 "0123"이 된다.
*/
@Override public String toString() {...}
// 포맷을 명시하지 않을 경우
/*
* 이 약물에 관한 대략적인 설명을 반환한다.
* 다음은 이 설명의 일반적인 형태이나, 상세 형식은 추후 변경될 수 있다.
*/
@Override public String toString() {...}
- toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자.
- 예를 들어, 앞서 생성한 PhoneNumber 클래스는 지역코드, 프리픽스, 가입자 번호용 접근자를 제공해야 한다.
- 이 정보가 필요한 프로그래머는 toString 반환값을 파싱하여야 하는데, 성능 문제와 불필요한 작업을 수반한다.
- 정적 유틸리티 클래스는 toString을 제공할 이요가 없으며, 대부분의 열거 타입도 자바가 이미 완벽한 toString을 제공하고 있다.
- 하지만 하위 클래스들이 공유해야 할 문자열 표현이 있는 추상 클래스라면 toString을 재정의해주어야 한다.
- 예컨대 대다수의 컬렉션 구현체는 추상 컬렉션 클래스들의 toString메서드를 상속해 사용한다.