Object 클래스의 toString()을 오버라이딩하는 이유
toString()이란
- 객체를 문자열로 표현하는 값을 반환하는 메서드이다.
- Object 클래스에 정의되어 있다.
- 기본 반환값은 “객체의 타입인 클래스의 이름 + @ + 객체의 해시 코드를 표현한 값(서명되지 않은 16진수로 표현)”이다.
Person@15db9742
왜 오버라이딩 할까?
- 객체가 자신을 사람이 읽을 수 있는 형태로 표현하는 공식적인 방법 중 유일한 수단이다.
- 개인적인 의견으로는 이게 toString()을 오버라이딩하는 가장 중요한 이유라고 생각한다. 개발자들은 사람이니까.
- 모든 클래스들이 상속하는 Object 클래스에서 객체가 자신을 사람이 읽을 수 있는 형태로 표현하도록 하는 메서드는 toString()이 유일하다.
- toString()이 없으면 커스텀 메서드를 만들어야 한다.
// 개발자: 뭐라는거야? User@1a2b3c // 개발자: 이해 완료! User[id=101, name=John, role=ADMIN] - 객체의 상태를 나타낼 수 있는 수단이 된다.
- 객체의 상태(값이 할당된 필드)는 객체의 근본이기 때문에 매우 중요하다.
- 객체는 자신의 필드와 그 값에 의해 정의되기 때문이다.
- Account는 잔액, 계좌번호, 소유자로 결정된다.
- Car는 생산업체, 색, 속도, 연비로 결정된다.
- Person은 이름, 나이, 주소로 결정된다.
- Java의 Record가 hashcode(), equals() 뿐 아니라 toString()도 자동적으로 오버라이딩 해주는 의미에 대해 생각해보자.
- 그리고 오버라이딩해주는 문자열의 내용이 왜 필드의 이름과 값의 쌍일지 생각해보자.
// User 레코드의 toString() 값 예시 User[id=101, name=John, role=ADMIN] - 객체의 상태(값이 할당된 필드)는 객체의 근본이기 때문에 매우 중요하다.
- 유의미한 정보를 디버깅 및 로깅에 사용할 수 있다.
- 특히 프로덕션 환경에서 문제가 발생했을 때 유용하다.
- 운영중인 프로덕션 환경은 디버거(debugger)를 적용할 수 없다. 이 때 문제 상황에 대한 정보를 알려줄 수 있는 건 로깅이다.
- 로깅에 사용되는 메서드가 toString()이다. toString()은 객체가 스스로를 문자열로 표현하는 수단이기 때문이다.
// 모든 Java의 Exception들이 상속하는 Throwable도 // 자신만의 toString()이 있음을 확인할 수 있다. public String toString() { String s = getClass().getName(); String message = getLocalizedMessage(); return (message != null) ? (s + ": " + message) : s; }- toString()의 기본적 반환값인 “클래스 이름 + @ + 해시코드 표상”은 개발자에게 어떠한 정보도 제공하지 못한다.
// 테스트를 실패했는데 이런 메시지가 뜬다면 // 뭐 어쩌라고? 라는 생각이 들 것이다. // 필드의 값이 달랐다던지, 예상한 값이 아니었다던지 등을 보여야지. Expected: User@1a2b3c but was: User@4d5e6f - 특히 프로덕션 환경에서 문제가 발생했을 때 유용하다.
- 외부 서비스와 상호작용할 때 객체를 전달할 수 있는 수단이 된다.
- 문자열은 모든 프로그램 및 프로그래밍 언어가 처리할 수 있는 자료이다.
- 즉, 자바 애플리케이션에서 객체를 외부로 내보내기 위해 문자열로 바꿔야 하는 순간이 언젠가는 온다.
- 이 때 객체에 대한 정보를 가져오기 위해 toString()을 사용할 수 있다.
➕ 양방향적 관계에 있는 클래스에서 toString()을 오버라이딩할 때 주의해야 하는 이유
- 클래스에 있어 양방향적 관계란 서로가 서로를 참조하는 관계를 의미한다.
- 서로를 참조하기 때문에 무한 재귀 호출의 문제가 발생하여 StackOverflowError가 발생한다.
- 발생 과정
- ClassA와 ClassB가 서로를 참조한다.
- ClassA의 toString()을 부른다.
- ClassA의 toString()은 ClassB의 toString()이 필요하다.
- ClassB의 toString() 역시 ClassA의 toString()이 필요하다.
- 서로가 서로를 호출하다가 StackOverflowError가 발생한다.
- toString()을 오버라이딩할 때 무한 재귀 호출을 일으키지 않는 필드만 사용하거나, id 필드만 사용하는 등의 방법으로 방지한다.
- 알아보고 난 뒤 내가 내린 결론: 양방향적 관계에 있는 클래스에서 toString()을 오버라이딩하는 게 문제가 아니라, 클래스들이 양방향적 관계에 있는 것 자체가 문제다.