본문 바로가기
Effective Java

[Effective Java] 아이템12 toString을 항상 재정의하라

by byeongoo 2021. 1. 24.

■ toString 재정의 이유 및 방법

  • toString을 잘 구현한 클래스는 사용하기 편하고, 그 클래스에 대해서 디버깅하기 쉽다.
    • map 객체를 출력하는 경우 {Jenny=PhoneNumber@adbbd} 보다는 {Jenny=707-867-5309}라는 메시지가 나오는게 훨씬 사용성이 좋다.
  • 실전에서는 toString 메서드를 재작성 할 때 그 객체가 가진 주요 정보를 모두 반환하는게 좋다
  • toString을 구현할 때면 반환값의 포맷을 문서화할지 정해야한다.
    • 포맷을 명시하면 그 객체는 표준적이고 명확하고 사람이 읽을 수 있게 된다.
    • 포맷을 명시하기로 했으면 해당 포맷에 맞는 문장렬과 객체를 상호전환할 수 있는 정적 팩터리나 생성자를 함께 제공하면 좋다.
    • 단, 포맷을 한번 명시하면 평생 그 포맷에 얽매이게 된다.
    • 포맷을 명시하지 않는다면 다음 릴리즈에 포맷을 변경할 수 있는 유연성을 더 가져간다
  • 포맷을 명시하든 안하든 개발자의 의도는 명확히 밝혀야 한다.
  • toString이 반환한 값에 대해 포함된 정보를 얻을 수 있는 API를 제공하자
    • toString에 있는 getter를 제공하지 않는다면, 클라이언트에서 toString을 파싱해서 사용할지도 모른다.
@RunWith(JUnit4.class)
public class ToStringTest {

    @Test
    public void toString테스트() {
        String phoneNumber = "707-908-9999";
        assertEquals(PhoneNumber.parse(phoneNumber), new PhoneNumber(707, 908, 9999));
    }

    @Test(expected = UnknownFormatConversionException.class)
    public void 파싱문자열_오류_테스트() {
        String phoneNumber = "707-908";
        assertEquals(PhoneNumber.parse(phoneNumber), new PhoneNumber(707, 908, null));
    }


    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class PhoneNumber{
        private Integer areaCode;
        private Integer prefix;
        private Integer lineNum;

        private static final Pattern phoneNumberPattern = Pattern.compile("^\\d{3}-\\d{3}-\\d{4}$");

        @Override
        public String toString() {
            return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
        }

        public static PhoneNumber parse(String phoneNumber) {

            if(!phoneNumberPattern.matcher(phoneNumber).find()) {
                throw new UnknownFormatConversionException(phoneNumber + " cannot be parsed");
            }

            String[] numbers = phoneNumber.split("-");
            return PhoneNumber.builder()
                    .areaCode(Integer.parseInt(numbers[0]))
                    .prefix(Integer.parseInt(numbers[1]))
                    .lineNum(Integer.parseInt(numbers[2]))
                    .build();
        }
    }

 

■ toString을 재정의 안해도 되는 경우

  • 정적 Utils 클래스는 상태를 가지는 클래스가 아니기 때문에 따로 재정의하지 않아도 된다.
  • enum타입 또한 이미 완벽한 toString을 제공한다.
  • 대다수의 컬렉션 구현체는 추상 컬렉션 클래스의 toString 메서드를 상속하여 사용한다.
  • 라이브러리를 통해 자동 생성하자
    • 구글의 @Autovalue
    • Lombok의 @ToString

롬복의 @ToString 사용시 양방향 연관 관계일 경우 무한 순한 참조가 발생할 수 있으므로 ToString에서 제외할 항목을 선택할 수 있다.

@ToString(exclude = "number")