Backend/Java

[Effective Java : Item 12] toString을 항상 재정의하라

김세진 2024. 2. 4. 20:47
반응형

 

 

 

 

 

개요

 

Object 클래스에서 기본으로 작성된 toString 메서드는 PhoneNumber@adbbd 처럼 단순히 '클래스 이름@16진수 해시코드' 로 반환한다. 

 

// Object의 toString 기본 메서드
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

 

하지만 위에서 언급한 형태보다는 010-1234-5678 처럼 실제 그 클래스의 핵심적인 정보를 읽기 쉬운 형태로 제공하는 것이 좋을 것이다. 이는 toString의 일반 규약이고, 실제로 모든 Object의 하위 클래스는 toString을 재정의하라고 규약을 정의하고 있다.

 

개발자가 직접 toString을 사용하지 않더라도, 디버깅 및 로깅을 할 때 객체를 출력할 경우 해당 메서드를 직접 호출하지 않더라도 사용될 여지가 있다. 따라서 toString을 재정의하지 않을 경우 무의미한 메시지만 로그에 남게 될 수 있다. 한편 디버깅 시 (잘 재정의된)toString을 사용하는 것은 권장된다는 의견이 주류이다.

 

 

 


실전

 

@AllArgsConstructor
public class PhoneNumber {
    private int areaCode;
    private int prefix;
    private int lineNum;

    public static void main(String[] args) {
        PhoneNumber phoneNumber = new PhoneNumber(707, 867, 5309);
        System.out.println(phoneNumber);
    }
}

 

PhoneNumber 클래스를 생성했고 이를 인스턴스화하여 println으로 출력하는 코드를 작성했다. toString을 재정의하지 않았으므로 이를 실행하면 아래와 같은 결과가 나온다.

 

 

객체의 해시코드가 표시되긴 하지만, 주요한 정보는 확인할 수 없다.

 

IntelliJ에서 제공하는 toString() Generate 기능

 

직접 toString을 재정의하지 않아도, Lombok을 사용하거나 위와 같이 특정 IDE에서는 toString을 자동으로 generate 하는 기능을 제공한다. 아래는 해당 기능을 사용하여 재정의한 toString이다. 

 

IDE 활용

@Override
public String toString() {
    return "PhoneNumber{" +
            "areaCode=" + areaCode +
            ", prefix=" + prefix +
            ", lineNum=" + lineNum +
            '}';
}

 

 

Lombok 활용

Lombok을 사용하고자 할 경우, @ToString 어노테이션만 클래스 앞에 선언하면 자동으로 모든 필드에 대한 toString 메서드를 재정의해준다. 혹은 @Data 를 사용할 경우 getter와 setter 뿐 아니라 toString도 재정의해준다.

 

@ToString
@AllArgsConstructor
public class PhoneNumber {
    private int areaCode;
    private int prefix;
    private int lineNum;

    public static void main(String[] args) {
        PhoneNumber phoneNumber = new PhoneNumber(707, 867, 5309);
        System.out.println(phoneNumber);
    }
}

 

 

출력에서 제외하고자 하는 필드가 있다면 아래와 같이 exclude 옵션을 활용하면 가능하다.

@ToString(exclude = {"areaCode", "prefix"})

 

직접 재정의

위의 두 가지 방법도 PhoneNumber 클래스에 대한 정보를 모두 확인할 수 있지만, 해당 형태보다는 707-867-5309 와 같은 전화번호 형태로 클래스 정보를 전달받는걸 원한다고 하자. 그렇다면 toString을 직접 재정의하여 원하는 포맷으로 결과값을 반환하면 된다.

 

@Override
public String toString() {
    return "PhoneNumber: " + areaCode + "-" + prefix + "-" + lineNum;
}

 

 

한편 toString이 반환하는 값에 대한 정보를 얻고 싶을 때, 접근자가 없다면 이를 개발자가 직접 parsing해서 사용해야 하는 번거로움이 생기게 된다. 이를 대비하여 클래스 내에 toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하도록 하자. 예시를 예로 들면 areaCode, prefix, lineNum에 대한 getter를 제공하는 것이다.

 

주의할 점

실제 프로젝트에서 toString을 사용할 때에는 그 객체가 가진 주요 정보를 모두 반환하는 것이 좋다. 단, 주의할 점 또한 존재한다.

 

1. 보안에 문제 되는 정보

보안 정책으로 인해 노출되지 않아야 하는 필드가 toString으로 인해 노출되어 로그나 클라이언트에서 조회될 수 있다면 문제가 될 것이다. 실수로 이런 정보를 포함하여 반환하지 않도록 주의해야 한다.

 

2. 객체가 지나치게 큰 경우

예를 들어 "맨해튼 거주자 전화번호부" 같은 객체 정보에 약 10만개가 넘는 전화번호 정보가 존재하고 이를 toString으로 그대로 문자열로 반환하게 한다면 큰 문제가 될 것이다. 이러한 경우는 요약 정보를 반환하도록 작성하자.

 

3. JPA를 사용할 경우 무한 순환 참조 문제

JPA를 사용하는 프로젝트에서 특정 개체와 양방향 관계인 개체를 toString으로 출력하고자 할 때, 서로 상대 개체에 대한 참조가 존재한다면 무한 순환 참조가 발생하여 Stack overflow를 발생시킬 수 있다. 이러한 경우 연관된 개체에 대한 출력을 피하거나 개체 자체가 아닌 단순한 정보만을 출력하도록 직접 재정의하도록 하자.

 

 

 


예외

 

정적 유틸리티 클래스

정적 유틸리티 클래스는 인스턴스를 만들어 사용하는 것이 아니므로 굳이 toString을 재정의할 이유가 없다.

 

열거(Enum) 타입

열거 타입 클래스의 경우 이미 자바가 완벽한 toString 의 구현을 제공하므로 따로 재정의하지 않아도 된다.

 

 

 


정리

 

1. 상위 클래스에서 알맞게 재정의된 경우를 제외하고는 toString을 재정의하도록 하자.

2. 해당 클래스에 대해 유용한 정보를 읽기 쉬운 형태로 제공하자.

 

 

 

 

 

반응형