[Effective Java] 아이템12. toString을 항상 재정의하라
요약
- toString을 재정의해야하는 이유
- toString을 재정의할 때 유의할점
- format을 지정할 때 주의할 점
- toString을 재정의 할 필요가 없는 경우
1. toString을 재정의해야하는 이유
toString 일반 규약
간결하면서 사람이 읽기 쉬운 형태의 정보를 제공해야 한다.
Object의 toString
Object에서 제공하는 toString은 클래스이름@16진수_해시코드를 반환한다.
public String toString() {
return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}
toString을 재정의하지 않는다면 로그메시지에 16진수 해시코드만 잔뜩 남을 것!
로그는 정보성/디버깅 용도로 남기는데, 의미있는 정보를 얻기 힘들다.
[예시] toString을 재정의하지 않고 PhoneNumber 객체를 출력할 때
{PhoneNumber@adbbd}
-> 대신 PhoneNumber에서 중요한 필드인 핸드폰 번호를 출력하도록 재정의하면,
{Jenny=707-867-5309} 와 같이 전화번호를 반환하는 형태가 되어 디버깅에 훨씬 유용하다.
[참고] 출력문에서 toString()이 어떻게 호출되는가?
public void println(Object x) {
String s = String.valueOf(x); // 출력시 출력값을 valueOf로 생성한다.
synchronized (this) {
print(s);
newLine();
}
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString(); // toString()을 사용한다.
}
2. toString()을 재정의 할 때 유의할 점
- 객체가 가진 주요정보를 모두 반환하는게 좋다.
- 객체 스스로를 완벽히 설명하는 문자열이여야 한다.
- toString이 반환한 정보를 얻어올 수 있는 API를 제공해야 한다. (없다면 개발자가 파싱해서 가져와야 하므로 성능 나빠진다.)
3. format을 지정할 때 주의할 점
1. 값 클래스(ex: 전화번호, 행렬)라면 formatting을 해주는 것이 좋다.
- 값 만으로는 자신의 정보를 충분히 설명하지 못하는 경우가 많다.
- 포맷팅한다면 그 값을 그대로 입출력에 사용하거나 csv파일로 사람이 읽을 수 있는 데이터 객체로 저장할 수 있다.
2. 포맷문자열과 객체를 상호전환할 수 있는 정적 팩터리나 생성자를 함께 제공하면 좋다.
ex) BigInteger, BigDecimal
의 toString(): 지정한 radix(진수)로 변환하여 출력한다.
지정한 radix 범위를 넘어가면 (2~36) 10진수를 디폴트로 변환한다.
BigInteger bigInteger1 = new BigInteger(321456);
bigInteger1.toString(2); // 1001110011110110000
10진수로 다시 변환 가능
public BigInteger(String val) {
this(val, 10);
}
3. 한 번 포맷을 지정하면 형태를 바꾸는 것이 어려우므로 초기에 문제 없는 값으로 포맷팅 해야 한다.
바꾸면 기존 데이터, 코드들에 영향을 끼친다.
새 포맷으로 적용한 것들이 기존 코드에 적용되지 못하고, 새롭게 만든 코드에는 기존 데이터가 적용되지 못한다.
실제로 운영중인 서비스라면, 수정이 누락된 경우가 흔하게 발생할 수 있고 -> 이는 사용자가 체감하는 심한 에러를 발생시킬 수 있다.
4. 명세에 아주 자세하게 적어야한다.
[포맷을 명시하는 경우]
/**
* 이 전화번호의 문자열 표현을 반환한다.
* 이 문자열은 "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() { ... }
4. toString()을 재정의할 필요가 없는 경우
1. 유틸성 정적 클래스
- toString()의 니즈가 없다.
2. ENUM
- 이미 적절한 toString을 제공한다.
- values()로 모든 value값을 가져올 수 있다.
- name, value 원하는 값을 가져올 수 있다.
+) 하위클래스들이 공유할 문자열이 있는 추상클래스라면 재정의 해야한다.