...
toString 메소드
기본적으로 Object 클래스의 toString() 메소드는 해당 인스턴스에 대한 정보를 문자열로 반환한다. 이 메서드는 인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의되어 있는 것이다.
이때 반환되는 문자열은 클래스 이름과 함께 구분자로 @가 사용되며, 그 뒤로 16진수 해시 코드(hash code)가 추가된다. 해시 코드 값은 인스턴스의 주소를 해싱하여 변환한 값으로, 고유 숫자로서 인스턴스마다 모두 다르게 반환된다.
실제로 toString() 메서드 내부를 본다면 다음과 같이 구현되어있다.
class MyObject extends Object { // extends Object 는 생략해 줘도 된다.
int objId;
String objName;
public MyObject(int objId, String objName) {
this.objId = objId;
this.objName = objName;
}
}
public class Main {
public static void main(String[] args) {
MyObject o1 = new MyObject(101, "First Object");
MyObject o2 = new MyObject(102, "Second Object");
// 클래스 타입 이름 @ 객체주소 해시코드
System.out.println(o1.toString()); // MyObject@251a69d7
System.out.println(o2.toString()); // MyObject@7344699f
}
}
이때 객체를 출력할때 toString() 메서드를 붙여주지 않고 변수만 출력해도 메서드를 붙인것과 똑같은 값이 출력되는데, 이는 컴파일러가 객체만 출력할 경우 자동으로 toString()을 붙여주고 컴파일 하기 때문이다.
System.out.println(o1.toString()); // MyObject@251a69d7
System.out.println(o1); // MyObject@251a69d7
toString 오버라이딩
하지만 만일 객체의 이름이나 주소값이 아닌 객체의 고유 정보를 출력하고 싶을 때가 있다.
예를 들어 다음 Person 객체를 출력하면 원론적인 값이 아닌, Person의 이름이나 나이 같은 고유 정보를 출력하고 싶을 때 바로 오버라이딩(Overriding)을 통해 toString() 메소드를 재정의 해주면 된다.
메서드 오버라이딩을 할때 바로 뒤에서 배울 접근제어자 지정에 대해 조심해야 할 점이 있다.
바로 부모 메서드에 정의된 접근제어자의 범위보다 낮은 범위의 제어자를 지정할수 없다는 것인데, Object 클래스의 toString 메서드의 접근제어자는protected void toString()라고 정의되어있어서, 이것을 오버라이딩 했을때 같은
protected 나 public 으로 설정해야 오버라이딩이 된다. 만일 private 나 default 제어자로 오버라이딩을 하려고 하면 컴파일 에러가 생긴다.
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// @Overriding : 오버라이딩은 메서드 시그니처가 일치해야 한다. 블록 안의 내용만 재정의 하는 것이다.
public String toString() {
return String.format("이름 : %s, 나이 : %d세", this.name, this.age);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("홍길동", 54);
// p1 객체를 출력하면 이름과 나이가 출력
System.out.println(p1); // 이름 : 홍길동, 나이 : 54세
}
}
실제로 자바의 Point 클래스도 toString() 으로 출력을 해보면 클래스명@주소값으로 나오는게 아니라 위와 같은 방식으로 재정의된 문자열 값이 나온다.
Point point = new Point(10, 20);
System.out.println(point.toString()); // java.awt.Point[x=10,y=20]
배열 요소 출력을 위한 재정의
아직 왜 toString을 오버라이딩해야되는지 마음속으로 잘 와닿지 않으면 이번 예시로 완벽히 이해가 될 것이다.
예를들어 배열에 다음 MyInt 라는 객체 자료를 저장해서 출력하고 싶다고 하자.
MyInt 클래스는 정수를 인자로 받아 100을 곱한 수를 저장하는 특별한 객체 자료형이다. 이를 배열에 적재하고 배열을 출력해보자.
import java.util.Arrays;
class MyInt {
final int num;
MyInt(int num) {
this.num = num * 100;
}
}
public class Main {
public static void main(String[] args) {
Object[] arr = new Object[5];
arr[0] = new MyInt(0);
arr[1] = new MyInt(1);
arr[2] = new MyInt(2);
arr[3] = new MyInt(3);
arr[4] = new MyInt(4);
System.out.println(Arrays.toString(arr));
}
}
우리는 [100, 200, 300, 400, 500] 으로 출력되기를 기대했지만, 엉뚱하게 객체 정보값이 출력되어 버렸다.
배열을 스트링으로 출력하기 위해 Arrays.toString() 메서드를 사용했어도 말이다.
이런식으로 출력된 이유는 Arrays.toString() 메서드는 배열 자체를 스트링화 한것이고, 배열 내의 각각의 원소들은 스트링화 하지 않기 때문에 발생한 현상이다.
따라서 배열 요소들도 정수값으로 출력되게 하고 싶다면, 그 객체 요소의 클래스 정의문에 toString() 을 재정의해야 한다.
즉, 핵심은 배열의 원소들이 int형 같은 primitive 타입이 아닌 객체와 같은 reference 타입일 경우, 배열을 출력할때 안에 있는 값을 출력하기 위해선 반드시 일일히 각 객체의 클래스마다 toString() 을 재정의해야 된다는 소리이다.
import java.util.Arrays;
class MyInt {
final int num;
MyInt(int num) {
this.num = num * 100;
}
@Override
public String toString() {
return Integer.toString(num);
}
}
public class Main {
public static void main(String[] args) {
Object[] arr = new Object[5];
arr[0] = new MyInt(1);
arr[1] = new MyInt(2);
arr[2] = new MyInt(3);
arr[3] = new MyInt(4);
arr[4] = new MyInt(5);
System.out.println(Arrays.toString(arr));
}
}
실제로 Integer 클래스에서도 다음과 같이 toString 메서드가 미리 오버라이딩 되어 있기 때문에 별다른 작업없이 배열을 출력하면 Wrapper 객체가 문자열로 변환된 것이다.
public class Main {
public static void main(String[] args) {
Object[] arr = new Object[5];
arr[0] = new Integer(1);
arr[1] = new Integer(2);
arr[2] = new Integer(3);
arr[3] = new Integer(4);
arr[4] = new Integer(5);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5]
}
}
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.