[Java] toString()을 재정의 하라(아이템 12)

우노구나·2025년 8월 4일

조슈아 블로크의 Effective Java3의 12번째 아이템은 toString()의 재정의에 관한 내용을 다룬다.

이 챕터는 요약하자면,

모든 하위 클래스에 toString()을 재정의 해야한다.
의미 있게 재정의해두면 디버깅, 로그, 출력 모두 훨씬 쉬워지기 때문이다.

이걸 보고 바로 깨달음을 얻는 사람이 있을 수도 있지만 나는 개인적으로 무슨 말인지 잘 이해가 되지 않았다.

이 내용을 제대로 이해하려면 약간의 사전지식이 필요하다.



toString()이란?

모든 클래스의 가장 최상위 클래스인 Object 클래스가 갖고 있는 메소드
객체를 사람이 읽을 수 있는 문자열로 표현할 때 사용된다.

기본적으로 객체의 toString()은 클래스_이름@16진수로_표시된_해시코드를 반환한다.

public class Car {}

Car car = new Car();
car.toString() // Car@6bc7c054

이처럼 크게 도움이 되지 않는 정보를 반환한다.



toString()의 자동호출

문제는 별로 도움이 되지 않는 정보를 주는 toString()이 생각보다 많은 곳에서 자동으로 호출되는 것이다.

자동호출되는 상황

  • 객체를 출력(printf , println) 할 때
  • 문자열과 + 연산할 때 ("car: " + obj)
  • 로깅 (log.info("{}", obj)) 할 때
  • 디버거가 객체를 출력 할 때
    등등 알게 모르게 자동으로 호출된다.

이처럼 다양한 경우에서 toString()이 호출되는데 이럴때마다 의미 없는 정보 대신 재정의를 통해 의미있는 정보를 반환하면 해당 클래스를 디버깅, 로그, 출력이 쉬워진다.



재정의 예시 코드

public class Person {
    private final String name;
    private final int age;

    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + "]";
    }
}


좋은 toString() 재정의 팁

1. 해당 객체가 가진 주요 정보를 모두 반환하는게 좋다.

주요 정보를 반환하지 않는 경우 다양한 문제가 발생하는데,

대표적인 예로는

  • 테스트 할 때 어떤 요소 때문에 테스트가 실패했는지 알 수 없는 상황이 발생한다.
  • 디버깅할 때 많은 불편은 겪게 된다.

2. 포맷을 명시하든 아니든 의도를 명확하게 밝혀야 한다.

toString()의 출력 형식을 외부에서 사용하거나 의존하게 할 거라면, 포맷을 정확하게 문서화해야한다.

하지만

toString() 구현이 그냥 디버깅, 로그용이라면 포맷이 바뀔 수 있다는 걸 명확하게 말해야한다.

코드로 예를 들면

포맷을 명시하는 경우 (공식 포맷)

/**
 * Returns a JSON-style string representation of this object.
 * Format: {"id":123,"name":"Kim"}	//포맷을 명시
 */
@Override
public String toString() {
    return "{\"id\":" + id + ",\"name\":\"" + name + "\"}";
}

디버깅용 toString() (포맷이 바뀔 수 있음)

/**
 * Returns a string representation for debugging purposes only.
 * The format is unspecified and subject to change.	//포맷이 바뀔 수 있다
 */
@Override
public String toString() {
    return "Person[id=" + id + ", name=" + name + "]";
}

이게 중요한 이유

이렇게 명확하게 명시를 해줘야 외부 시스템이나 다른 코드가 안전하게 toString()을 사용할 수 있다.

이를 명확하게 명시하지 않았을 때 생기는 문제로는 디버깅용 toString()에 의존적인 코드 또는 시스템을 만들었을 때, 해당 toString()을 수정할 때 의존한 부분들이 다 깨지는 문제(API 깨짐 현상)가 있다.


3. toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자.

toString()이 반환하는 문자열 안에 어떤 정보가 들어 있다면,
그 정보를 직접 꺼낼 수 있는 메서드(API)도 같이 제공하는게 좋다.

예를 들면

@Override
public String toString() {
    return "Person[id=1234, name=Kim]";
}

이러한 코드는 idname을 따로 가져오려면 toString()으로 받은 문자열을 파싱해야하는 불필요한 작업을 거치게 된다.

또한, 이런 경우 toString이 수정될 경우 파싱에 문제가 발생해 시스템에 심각한 에러를 일으킬 수 있다.

그렇기 때문에

public class Person {
    private int id;
    private String name;

    public int getId() { return id; }
    public String getName() { return name; }

    @Override
    public String toString() {
        return "Person[id=" + id + ", name=" + name + "]";
    }
}

이런식으로 API를 제공하는게 바람직하다.

이처럼 toString은 출력용으로만 사용하는게 이상적이다.



마무리

이처럼 toString()을 제대로 재정의 하면 개발에 많은 도움을 준다. 반대로 잘 사용하지 못한다면 심각한 문제를 일으키기도 한다.

사실 Java에서 꽤나 많은 클래스에서 toString()의 재정의가 이미 이루어져 있다. (String, LocalDate, Exception, List 등)

하지만 하위 클래스나 직접 만든 클래스는 재정의가 되지 않았기 때문에 반드시 재정의를 해줘야한다.

profile
기술 블로그

0개의 댓글