-
Item 12: Always override toString독서/Effective Java 2021. 12. 12. 15:28
항상 toString을 재정의하라.
재정의의 이유
Object는 toString 메서드의 구현을 제공하지만 반환하는 문자열은 "일반적으로는" 내가 원하는 형태는 아니다.
package com.tistory.povia.effectivejava.item1; public class User { private static final int FIRST_AGE = 1; private final String firstName; private final String lastName; private int age; private User(String firstName, String lastName, int age){ this.firstName = firstName; this.lastName = lastName; this.age = age; } public static User newBaby(String firstName, User father){ return new User(firstName, father.lastName, FIRST_AGE); } public static User immigration(String firstName, String lastName, int age){ return new User(firstName, lastName, age); } public String firstName() { return firstName; } public String lastName() { return lastName; } }
이 클래스의 toString()을 바로 실행해보면 어떨까?(String 형태로 사용하기만 하면 자동으로 내부적으로 toString을 사용한다.)
father = User.immigration("boris", "Chak", 32); log.info("father = {}", father); // 실제 출력된 로그 father = com.tistory.povia.effectivejava.item1.User@b1712f3
출력된 결과물은 다음과 같은 구조로 이루어져 있다.
* 클래스 위치, 클래스 이름, @, 해시 코드의 부호 없는 16진수 표현
toString에 대한 일반 규약 = "간단하지만 사람이 읽기 쉬운 정보를 제공하는 표현"
여기에서의 toString이라면 결과가 "boris Chak, 32" 등으로 나와야 하지 않을까?
재정의, 그리고 장점
toString을 직접 구현했을 때의 장점은 다음과 같다.
1) 클래스를 훨씬 더 사용하기 편해진다.
2) 클래스를 디버그하기가 더 쉬워진다.
만약 위의 User 클래스에서 toString()를 재정의했다면?// 재정의 @Override public String toString() { return "User{" + "Name='" + firstName + ' ' + lastName + '\'' + ", age=" + age + '}'; } // 실제 로그 User{Name='boris Chak', age=32}
위의 Object에 대한 정보'만' 출력되던 것에서 벗어나 해당 클래스의 속성을 명시적으로 파악할 수 있게 되었다.
이로 인해 추가되는 장점은 다음과 같다.
예시로 하나의 가족에 3명의 구성원이 있다고 가정해보자.
@Test @DisplayName("자손이 태어나는 경우") void birthTest(){ List<User> family = new ArrayList<>(); family.add(father); family.add(User.immigration("cally", "hu", 30)); User son = User.newBaby("sonas", father); family.add(son); log.info("family = {}", family); Assertions.assertThat(son.lastName()).isEqualTo("Chak"); } // 실제 출력 family = [User{Name='boris Chak', age=32}, User{Name='cally hu', age=30}, User{Name='sonas Chak', age=1}]
toString을 재정의한 이후 family를 출력하면 원하던 대로 User를 한 눈에 파악할 수 있는 결과가 출력되었다.
재정의 시 주의해야할 점
- 그 Object가 가진 주요 정보 모두(여기서는 이름, 나이)를 반환하는게 좋다.
- 하지만 객체가 거대하거나 문자열로 표현하기 적합하지 않다면 => "family members count = 3" 등으로 요약 정보를 표시하는 것이 좋다.
- 반환값의 포맷을 문서화할지 정해야 한다
- 전화번호나 행렬 같은 값 클래스 => 문서화 하기를 권한다.
- 포맷을 명시하게 된다면 그 객체는 표준적이고, 명확하고, 사람이 읽을 수 있게 된다.
- 포맷을 명시하든 아니든 의도는 명확히 밝혀야 한다. 명시하려면 아주 정확하게 명시하자.
- 명시하기로 정했다면, 명시한 포맷에 맞는 문자열과 객체를 상호 전환할 수 있는 정적 팩토리나 생성자를 함께 제공해주면 좋다. ex) BigInteger, BigDecimal
// 명시화를 한 예시 /** * 사용자 정보를 문자열로 변환한다. * Name 은 이름 + " " + 성으로 구성한다. * age 는 나이이다. * * 출력 형식은 다음과 같다. * @return "User{Name=firstName + ' ' + lastName, age=age} */ @Override public String toString() { return "User{" + "Name='" + firstName + ' ' + lastName + '\'' + ", age=" + age + '}'; }
- 포맷 명시의 단점
- 한번 명시하면 평생 그 포맷에 얽매이게 된다.
- 처음에 명시한대로 프로그래머들이 코드를 작성할 것인데 추후에 포맷을 바꾼다면 이를 사용하던 코드들과 데이터들은 엉망이 될 것이다.
- 반대로 포맷을 명시하지 않는다면 향후 릴리스에서 정보를 더 넣거나 포맷을 개선할 수 있는 유연성을 얻게 된다.
불필요한 경우
- 정적 유틸리티 클래스
- 대부분의 열거 타입
- Annotation(자동 생성) 사용(구글의 @Autovalue, Lombok의 @ToString)
- Lombok @ToString은 주의해서 써야한다. 재정의시 순환참조를 주의하자.
'독서 > Effective Java' 카테고리의 다른 글
Item 14: Consider implementing Comparable (0) 2021.12.12 Item 11: Always override hashCode when you override equals (0) 2021.12.12 Item 9: Prefer try-with-resources to try-finally (0) 2021.12.05 Item 8: Avoid finalizers and cleaners (0) 2021.12.05 Item 6: Avoid creating unnecessary objects (0) 2021.12.04