Object의 기본 toString 메소드는 단순히 클래스_이름@16진수로_표시한_해시코드로 반환한다. ex) PhoneNumberB@adbbd
toString에 대한 내용과 일반 규약
- 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 반환
- 모든 하위 클래스에서 이 메소드를 재정의
equals와 hashCode 규약만큼 중요하진 않지만, 더 사용하기 좋고 디버깅이 쉽다.
어떻게 사용되고 사용해야 하나?
toString은 직접 호출하지 않더라도 다른 어딘가에서 호출된다.
예를들어 println, printf, 문자열 연결 연산자(+), assert 구문에 넘길 때, 디버거가 객체를 출력할 때 등에서 말이다.
만약 재정의를 하지 않으면 제일 처음에 말했던 별로 쓸모 없는 메시지가 호출된다.
그리고 그 객체가 가진 주요 정보 모두를 반환하는게 좋다.
하지만 객체가 거대하거나 객체 상태가 문자열로 표현하기 적합하지 않다면 무리가 있다. 이 때는 요약해서 전달하자
ex) 맨해튼 거주자 전화번호부(총 148732개)
toString 문서화 포맷
전화번호나 행렬 같은 값 클래스라면 문서화하기를 권한다.
포맷을 명시하면 그 객체는 표준적이고, 명확하고, 사람이 읽을 수 있게 된다. 입출력이나 csv 데이터 객체로 저장도 가능하다.
포맷을 명시하기로 했다면 명시한 포맷에 맞는 문자열과 객체를 상호 전환할 수 있는 정적 팩토리나 생성자를 함께 제공해주면 좋다.
책에서는 BigInteger와 BigDecimal을 들었고, 이외에 Long.toString(long) 메소드 등이 있다.
물론 단점도 있다. 포맷을 한 번 명시하면, 그 클래스가 많이 쓰인다면 평생 그 포맷에 얼매이게 된다.
만약 향후 릴리스에서 포맷을 바꾸면 기존의 코드와 데이터들은 엉망이될거다.
반대로 포맷을 명시하지 않으면 향후 릴리스에서 정보를 더 넣거나 포맷을 개선할 유연성을 가지게 된다.
명시하든 하지 않든 의도는 명확해야한다. 포맷을 명시하려면 아주 정확하게 하자.
아래는 포맷을 명시할 경우다.
/**
* 이 전화번호의 문자열 표현을 반환한다.
* 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다.
* XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다.
* 각각의 대문자는 10진수 숫자 하나를 나타낸다.
*
* 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면,
* 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면
* 전화번호의 마지막 네 문자는 "0123"이 된다.
*/
@Override public String toString() {
return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
}
아래는 명시하지 않을 경우 아래처럼 작성할 수 있을 것이다.
/**
* 이 약물에 관한 대략적인 설명을 반환한다.
* 다음은 이 설명의 일반적인 형태이나,
* 상세 형식은 정해지지 않았으며 향후 변경될 수 있다.
*
* "[약물 #9: 유형=사랑, 냄새=테레빈유, 겉모습=먹물]"
*/
@Override public String toString(){ ... }
설명을 읽고도 포맷에 맞춰 코딩, 특정 값을 뺴내 영구 저장한 것은 자신의 탓이다.
포맷 여부와 상관없이 toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자
phoneNumber 클래스는 지역코드, 프리픽스, 가입자 번호용 접근자를 제공해야한다. 그렇지 않다면 이 정보가 필요할 때,
toString의 반환값을 파싱할 수 밖에없다. 성능이 저하되고 필요하지 않은 작업이다.
Getter 혹은 유사한 것을 만들어서 제공하자.
접근자를 제공하지 않으면 변경될 수 있다고 문서화 했더라도 그 포멧이 사실상 준-표준 API나 다름없다.
주의
정적 유틸리티 클래스나 대부분의 열거 타입은 toString을 재정의 할 이유가 없다. 후자는 자바가 이미 완벽한 toString을 제공한다.
하지만 하위 클래스들이 공유해야 할 문자열 표현이 있는 추상 클래스라면 toString을 재정의 해주자.
예를들어 대다수 컬렉션 구현체는 추상 컬렉션 클래스들의 toString 메소드를 상속해서 사용한다.
정리
앞서 설명했던 구글의 AutoValue 프레임워크는 toString도 생성해준다.(대부분 IDE 포함)
AutoValue가 각 필드의 내용을 나타내 주지만 클래스 의미까지 파악하지는 못한다.
앞서 PhoneNumber 관련된 toString 코드는 자동생성에 적합하지 않다(전화번호는 표준체계가 있으니)
그러나 적어도 Object의 toString보다 나으니까 따로 코딩하지 않을거라면 자동생성이 더 유용하다.
약물(Potion) 관련된 toString 코드은 재정의해서 사용하기 적합하다.
모든 구체 클래스에서 Object의 toString을 재정의하자. 상위 클래스에서 이미 알맞게 재정의한 경우는 예외다.
* 위 글은 EffectiveJava 3/E 책 내용을 정리한 글로, 저작권 관련 문제가 있다면 댓글로 남겨주시면 즉각 삭제조치 하겠습니다.
'Reading > Effective Java' 카테고리의 다른 글
[Effective-Java] Item 14. Comparable을 구현할지 고려하라 (0) | 2021.08.28 |
---|---|
[Effective-Java] Item 13. clone 재정의는 주의해서 진행하라 (0) | 2021.08.23 |
[Effective-Java] Item 11. equals를 재정의하려거든 hashcode도 재정의하라 (0) | 2021.08.20 |
[Effective-Java] Item 10. equals는 일반 규약을 지켜 재정의하라 (0) | 2021.08.19 |
[Effective-Java] Item 9. try-finally보다는 try-with-resources를 사용하라 (0) | 2021.08.18 |