반응형

ITEM 12 "toString 을 항상 재정의하라"

 

이번 장에서는 Object 의 toString 을 항상 재정의하라고 권고하고 있다.

equals 와 hashCode 규약처럼 중요하진 않지만, toString 을 잘 재정의한 클래스는 사용하기 편하고 디버깅하기 쉽다.

기본 toString 메서드는 클래스_이름@16진수로_표시한_해시코드 만 반환하는데 이는 사람이 읽기 어려울 뿐 아니라 쓸모 없는 메세지만 로그에 남을 것이다.

 

가급적 그 객체가 가진 주요 정보 모두를 반환하는게 좋다. 객체가 크거나 문자열로 표현하기에 적합하지 않다면 요약 정보로 표현해도 된다.

 

또 반환값의 포맷을 결정하고 문서화하면 좋다. 포맷을 명시하면 표준적이고 명확하며 사람이 읽을 수 있게 된다. 추가로 toString 포맷으로 생성할 수 있는 생성자나 정적 팩토리 메서드를 같이 정의하면 금상첨화다.

 

BigInteger String 을 매개변수로 받는 생성자

 

BigInteger 클래스에서 제공하는 toString 반환 포맷으로 생성자를 호출할 수 있게 구성되어 있는 것을 볼 수 있다.

 

하지만, 한 번 포맷을 명시하면 평생 그 포맷에 얽매이게 되어 다음 릴리스에서 포맷을 바꾸면 이를 사용하던 코드들 모두 변경해야 되는 단점이 있다. 이 반대 개념으로 포맷을 명시하지 않는다면 포맷에 얽매이지 않고 프로그래밍할 수 있다. 향후 릴리스에서 추가로 정보를 넣거나 포맷을 개선할 수 있는 유연함을 가지게 된다. 어떠한 방법이던 포맷을 명시하든 아니든 의도는 분명히 알 수 있도록 주석은 달아두자.

 

책에서는 포맷 명시 여부와 상관없이 toString 반환 값에 포함된 정보를 얻어올 수 있는 API 를 제공해야 한다고 하는데 나는 이 API 가 getter 메서드를 말하는 것 같았다. 프로그래머가 직접 파싱해서 정보를 얻어오는 폐해를 피하고자 단순히 필드 값들을 제공해주면 되니깐 말이다.

 

public class EffectiveJavaTest {
    @Test
    public void 전화번호_테스트() {
        PhoneNumber testPhone = new PhoneNumber("010", "1234", "5678");
        System.out.println(testPhone);
        PhoneNumber testPhone2 = PhoneNumber.createByString(testPhone.toString());
        System.out.println(testPhone2);
    }

    @Builder
    @Getter
    public static class PhoneNumber {
        protected String firstNumber;
        protected String secondNumber;
        protected String thirdNumber;

        private static final Pattern phoneNumberPattern = Pattern.compile("^\\d{3}-\\d{4}-\\d{4}$");
        public PhoneNumber(String firstNumber, String secondNumber, String thirdNumber) {
            this.firstNumber = firstNumber;
            this.secondNumber = secondNumber;
            this.thirdNumber = thirdNumber;
        }

        /*
         * toString 메서드는 전화번호의 문자열 표현을 반환한다.
         * 010-xxxx-yyyy 형태의 11글자로 구성된다.
         * 전화번호의 각 부분 값이 너무 작아서 자릿수를 채울 수 없다면,
         * 앞에서부터 0을 채워나간다. 예컨데 중간 번호가 123이라면, 0123 으로 표기한다.
         * */
        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append(firstNumber);
            sb.append("-");
            sb.append(secondNumber);
            sb.append("-");
            sb.append(thirdNumber);
            return sb.toString();
        }

        public static PhoneNumber createByString(String phoneNumber) {
            if(!phoneNumberPattern.matcher(phoneNumber).find()) {
                throw new IllegalArgumentException(phoneNumber + " cannot be parsed");
            }

            String[] numbers = phoneNumber.split("-");
            return PhoneNumber.builder()
                    .firstNumber(numbers[0])
                    .secondNumber(numbers[1])
                    .thirdNumber(numbers[2])
                    .build();
        }
    }
}

 

책에서 권장하는 방법들을 모두 적용해 보았다.

 

"toString 을 항상 재정의하고 문서화하자

toString 반환 값으로 생성할 수 있는 생성자도 마련하자"

반응형

+ Recent posts