1. toString()메서드 오버라이딩하는 이유
toString()
메서드는 Java의 모든 객체가 기본적으로 가지고 있는 메서드이며, 객체를 사람이 읽기 좋은 문자열로 표현하기 위해 사용됩니다. 위 코드에서 toString()
메서드를 재정의하는 이유는 다음과 같습니다:
1. 객체 상태를 출력하기 위해
- 기본
toString()
메서드는 객체의 클래스 이름과 해시코드(메모리 주소)를 반환합니다. 이 정보는 일반적으로 이해하기 어렵고, 객체의 실제 데이터를 확인할 수 없습니다. - 따라서
toString()
메서드를 재정의하여, 객체의 속성 값들을 문자열로 반환하도록 하면 객체의 상태를 쉽게 확인할 수 있습니다.
예:
Board board = new Board();
board.setSubject("Notice");
board.setWriter("Admin");
System.out.println(board); // toString()을 재정의한 결과 출력
출력:
Board [subject=Notice, content=null, password=null, writer=Admin, readhit=0, no=0, regdate=null]
2. 디버깅 및 로깅에 유용
- 객체의 상태를 디버깅하거나 로그에 기록할 때
toString()
메서드가 재정의되어 있다면 객체 정보를 빠르고 쉽게 확인할 수 있습니다. - 특히 디버깅 과정에서 어떤 데이터가 객체에 저장되어 있는지 확인하는 데 유용합니다.
3. 코드의 가독성 향상
toString()
메서드를 재정의하면, 객체를 출력할 때 해당 객체의 정보를 명확하고 일관되게 보여줄 수 있습니다.- 이는 코드 리뷰나 협업에서도 유용합니다.
4. 사용자 정의 포맷 제공
toString()
메서드를 통해 객체 정보를 원하는 형식으로 출력할 수 있습니다. 위 코드에서는Board
객체의 모든 필드를 보여주는 포맷으로 정의했습니다.
기본 toString()
과의 차이
- 기본 구현:(클래스 이름 +
@
+ 해시코드) Board@5e2de80c
- 재정의 후 구현:
Board [subject=Notice, content=Hello, password=1234, writer=Admin, readhit=5, no=1, regdate=2025-01-21]
재정의된 toString()
메서드는 객체 상태를 더 직관적으로 보여주므로, 일반적으로 toString()
을 재정의하는 것이 권장됩니다.
2. toString()메서드 오버라이딩하는 위치
꼭 객체가 처음 생성된 클래스에만 toString()
을 오버라이딩해야 하는 것은 아닙니다. 상황에 따라 다릅니다. 하지만 일반적으로 객체의 클래스를 정의할 때 해당 클래스 내부에 toString()
을 오버라이딩하는 것이 가장 적절합니다. 이유와 예시를 살펴보겠습니다.
1. toString()
을 오버라이딩하는 일반적인 위치
toString()
메서드는 객체를 설명하는 문자열을 반환하므로, 객체의 상태(필드 값 등)를 가장 잘 알고 있는 클래스에서 오버라이딩하는 것이 적합합니다. 즉, 해당 객체를 정의한 클래스에서 오버라이딩하는 것이 일반적입니다.
예:
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
2. 상속받은 클래스에서 오버라이딩 가능
클래스가 다른 클래스를 상속받고 있다면, 상속받은 클래스에서 toString()
을 오버라이딩하여 추가적인 정보를 포함할 수도 있습니다.
예:
class Employee extends Person {
String department;
Employee(String name, int age, String department) {
super(name, age);
this.department = department;
}
@Override
public String toString() {
return super.toString() + ", department='" + department + "'";
}
}
public class Main {
public static void main(String[] args) {
Employee emp = new Employee("Alice", 30, "HR");
System.out.println(emp);
// 출력: Person{name='Alice', age=30}, department='HR'
}
}
이처럼 상속받은 클래스에서 오버라이딩하면, 부모 클래스의 toString()
결과에 추가적인 정보를 더할 수 있습니다.
3. 특정 상황에서 외부에서 직접 toString()
정의 가능
toString()
을 클래스 내부에서 오버라이딩하지 않고, 외부에서 별도의 유틸리티 메서드로 toString()
의 동작을 정의할 수도 있습니다. 하지만 이 방법은 일반적이지 않고, 임시적으로 특정 상황에서 객체의 문자열 표현을 커스터마이즈할 때 주로 사용됩니다.
예:
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
// 특정 상황에서 임시로 문자열 정의
System.out.println(customToString(person));
}
public static String customToString(Person person) {
return "Custom Person: " + person.name + " (" + person.age + " years old)";
}
}
4. 상황에 따라 toString()
을 오버라이딩하지 않아도 되는 경우
toString()
을 굳이 오버라이딩하지 않아도 되는 경우:
- 클래스의 객체를 문자열로 표현할 필요가 없거나, 디버깅을 위해 기본
Object
의toString()
만으로 충분할 때. - 외부 로깅 라이브러리나 JSON 직렬화 도구(예: Jackson, Gson)를 사용할 경우, 이런 도구들이 객체의 필드를 문자열로 변환하는 작업을 대신 처리해줍니다.
결론
- 일반적으로
toString()
은 해당 객체를 정의한 클래스에서 오버라이딩합니다. - 상속 관계가 있다면 부모 클래스의
toString()
을 재활용하며 추가 정보를 포함할 수 있습니다. - 특별한 경우가 아니라면, 외부에서
toString()
의 동작을 정의하기보다는 객체 클래스 내부에서 오버라이딩하는 것이 깔끔하고 유지보수에 좋습니다.
3. 기본타입의 출력과 참조타입의 출력
기본 타입(primitive types)과 참조 타입(reference types)의 출력은 Java에서 동작 방식이 다릅니다! 차이를 간단히 정리하면 다음과 같습니다.
1. 기본 타입의 출력
기본 타입(예: int
, double
, char
, boolean
등)은 값 자체를 출력합니다.
- Java는 기본 타입에 대해 내부적으로 별도의 변환 작업 없이 바로 출력합니다.
toString()
메서드 호출이 필요하지 않습니다.
예제:
public class Main {
public static void main(String[] args) {
int number = 42;
double pi = 3.14;
char letter = 'A';
boolean isTrue = true;
System.out.println(number); // 42
System.out.println(pi); // 3.14
System.out.println(letter); // A
System.out.println(isTrue); // true
}
}
출력 결과:
42
3.14
A
true
2. 참조 타입의 출력
참조 타입(객체)은 System.out.println()
으로 출력할 때 toString()
메서드가 호출됩니다.
기본적으로 참조 타입은
Object
클래스의toString()
메서드를 상속받습니다.toString()
메서드를 오버라이딩하지 않으면 기본적으로 다음 형식으로 출력됩니다:클래스명@해시코드(16진수)
예제:
class MyClass {}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(obj); // MyClass@<해시코드>
}
}
출력 결과(예시):
MyClass@1a2b3c
- 만약 객체에 대해 의미 있는 정보를 출력하려면,
toString()
메서드를 오버라이딩해야 합니다.
toString()
오버라이딩 예제:
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 25);
System.out.println(person); // Person{name='Alice', age=25}
}
}
3. 주요 차이점 정리
특징 | 기본 타입 | 참조 타입 |
---|---|---|
데이터 | 값 자체를 저장 | 객체의 주소(참조)를 저장 |
출력 방식 | 값 자체가 출력됨 | toString() 메서드 호출 후 결과 출력 |
toString() 호출 여부 |
호출되지 않음 | 호출됨 (기본 구현은 클래스명@해시코드 ) |
출력 결과 | 값 자체 (예: 42 , true , A ) |
toString() 결과 |
4. 혼합 출력
기본 타입과 참조 타입을 함께 출력하는 경우에도 같은 규칙이 적용됩니다.
예제:
public class Main {
public static void main(String[] args) {
int number = 42;
String text = "Hello";
System.out.println("Number: " + number + ", Text: " + text);
}
}
출력 결과:
Number: 42, Text: Hello
결론
- 기본 타입은 값 자체가 출력되고,
toString()
이 호출되지 않습니다. - 참조 타입은
toString()
메서드가 호출되어 반환된 문자열이 출력됩니다. - 필요에 따라
toString()
메서드를 오버라이딩하여 객체 출력 결과를 커스터마이징할 수 있습니다.
4. print()와 println, 그리고 printf에서의 toString()
print()
와 println()
에서는 toString()
이 호출되지만, printf()
는 다르게 동작합니다. 이는 printf()
가 형식 지정자(Format Specifier)를 사용하기 때문입니다. 이를 구체적으로 설명하면 다음과 같습니다:
1. print()
와 println()
의 동작
print()
와println()
은 객체를 출력할 때toString()
메서드를 자동으로 호출합니다.- 기본 타입(예:
int
,double
)은 값 자체를 출력하고, 참조 타입(객체)은toString()
결과를 출력합니다.
예제:
class MyClass {
@Override
public String toString() {
return "Custom Object";
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.print(obj); // Custom Object
System.out.println(obj); // Custom Object
}
}
출력 결과:
Custom ObjectCustom Object
2. printf()
의 동작
printf()
는 형식 지정자(Format Specifier)를 사용해 값을 출력합니다.- 형식 지정자가 문자열(
%s
)이면 참조 타입의 경우toString()
메서드가 호출됩니다. - 그러나 다른 형식 지정자(
%d
,%f
,%c
등)를 사용하면toString()
이 호출되지 않고, 해당 형식에 맞는 데이터만 출력됩니다.
예제:
class MyClass {
@Override
public String toString() {
return "Custom Object";
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
// printf()에서 문자열 포맷 지정자 %s 사용
System.out.printf("%s\n", obj); // Custom Object (toString() 호출)
// 다른 형식 지정자 사용
int number = 42;
System.out.printf("%d\n", number); // 42 (toString() 호출 X)
}
}
출력 결과:
Custom Object
42
3. printf()
가 다르게 동작하는 이유
printf()
는 형식 지정자에 따라 동작을 다르게 처리합니다.- 예를 들어:
%s
: 문자열로 변환 (toString()
호출)%d
: 정수 출력 (기본 타입만 처리,toString()
호출 X)%f
: 실수 출력 (기본 타입만 처리,toString()
호출 X)
- 형식 지정자를 통해 데이터 타입을 명시적으로 지정하기 때문에,
printf()
는 객체 출력 시에도 항상toString()
을 호출하지 않습니다.
4. 차이점 요약
메서드 | 기본 타입 출력 | 참조 타입 출력 | toString() 호출 |
---|---|---|---|
print() |
값 자체 출력 | toString() 결과 출력 |
항상 호출 |
println() |
값 자체 출력 | toString() 결과 출력 |
항상 호출 |
printf() |
형식 지정자에 따라 값 출력 | %s 일 때만 toString() 호출 |
조건부 호출 |
5. 결론
print()
와println()
은 객체를 출력할 때 항상toString()
을 호출합니다.printf()
는 형식 지정자%s
를 사용할 때만toString()
을 호출하며, 다른 형식 지정자는toString()
없이 해당 형식에 맞는 값만 출력합니다.printf()
는 형식 지정자로 출력 방식을 더 세밀하게 제어할 수 있는 기능을 제공합니다.
'자바 > 객체지향 프로그래밍(OOP)' 카테고리의 다른 글
정적 클래스와 비정적 클래스 접근방식과 점 연산자 (1) | 2025.01.22 |
---|---|
업캐스팅(승급) (0) | 2025.01.21 |
super() 정리1 (0) | 2025.01.21 |
객체에 대한 이해. (0) | 2025.01.21 |
기본생성자와 암시적 생성자 (0) | 2025.01.15 |