Java, Object - toString

Object 클래스의 toString() 메소드는 객체의 정보를 문자열로 반환하여 제공한다.
디버깅에 로깅에 유용하며 이 메소드는 Object 클래스에 정의되어 모든 클래스에서 상속 받아 사용할 수 있다.

ToStringMain.java

public class ToStringMain {
    public static void main(String[] args) {
        Object obj = new Object();
        String str = obj.toString();

        // object 출력
        System.out.println(obj);
        
        // toString() 반환값
        System.out.println(str);
    }
}

출력 결과

java.lang.Object@10f87f48
java.lang.Object@10f87f48

Object 타입 변수, String 타입 변수 모두 똑같은 값을 출력하고 있다.


toString()

toString() 메소드를 보기 위해 Object 내부 코드로 이동하도록 한다.

Object.java

  • Object 클래스에서 toString() 메소드가 정의되어 있다.
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • getClass().getName() 패키지를 포함한 객체의 이름이다.
    • java.lang.Object 출력하는 것을 볼 수 있는데 java.lang 패키지에서 Object 클래스가 있다는 의미이다.
  • hashCode() 해시 값이 16bit 헥스 형태의 주소로 반환한다는 의미이다.
    • 이 객체의 참조 주소 값을 알기 위한 용도이다.

toString() 메소드 용도는 사용하여 해당 객체의 패키지와 클래스명, 참조 값 알려준다.


println(obj), println(str) 결과 같은 이유

앞서 ToStringMain 함수에서 실행한 println(..), println(..) 결과가 똑같은 동작을 한 것을 볼 수 있었다.

// object 출력
System.out.println(obj); // obj 변수는 Object 타입

// toString() 반환값
System.out.println(str); // str 변수는 String 타입

왜 똑같은지 System.in.println() 메소드를 살펴볼 필요가 있다.

PrintStream.java

public void println(Object x) {
    String s = String.valueOf(x);
    // ...
    print(s);
    // ...
}
  • Object 타입 객체가 온 경우, 오버로딩하여 println(Object x) 메소드를 호출한다.
  • print(s) 화면에 문자열을 출력하는 코드로 보인다. 인자 값 s 변수는 valueOf() 메소드를 사용하여 문자열을 반환한 것인데, 다시 파고들어 탐색한다.

String.java

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

여기서 Object 클래스의 toString() 메소드를 반환하여 문자열을 출력한 것을 볼 수 있다.

println() 오버로딩

다음으로 System.in.println() 메소드가 String 타입과 Object 타입을 받고 있다.

System.out.println(obj); // obj 변수는 Object 타입
System.out.println(str); // str 변수는 String 타입
  • PrintStream.java 내부를 살펴보면, println 메소드가 여러 개 정의되어 있다.
    타입에 따라 오버로딩하여 문자열을 출력하는 것이다.

즉, 앞서 만든 코드의 결과가 println(obj) , println(str) 동일한 역할의 로직을 수행하고 있다.


toString() 오버라이딩 재정의로 사용하기

Object.toString() 메소드는 클래스 정보와 참조 값을 제공하였다.
하지만 이러한 정보만으로 객체의 상태를 나타낼 수 없는데
자식 클래스에서 toString() 메소드를 오버라이딩하여 유용한 정보를 추가로 제공할 수 있다.

Cat.java

package object;

public class Cat {

    private String name;

    public Cat() {
        name = "I am Cat";
    }

    public Cat(String name) {
        this.name = name;
    }
    
    public void sound() {
        System.out.println("cat sound");
    }
}

Car.java

package object.toString;

public class Car {
    public void move() {
        System.out.println("car move");
    }
}

ToStringMain.java

package object.toString;

public class ToStringMain {
    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car.toString());

        Cat cat = new Cat("icefire");
        System.out.println(cat.toString());

    }
}

출력 결과

object.toString.Car@b4c966a
Cat Name: icefire

정적 메소드로 toString() 프린트하기

신규 클래스를 만들어 정적 메소드로 Object 인수를 받아 toString(); 정보를 출력하는 것이다.

ObjectPrint.java

package object.toString;

public class ObjectPrint {
    public void getInfo(Object obj) {
        String str = "(Debug) 객체 정보: " + obj.toString();
        System.out.println(str);
    }
}

ToStringMain.java

package object.toString;

public class ToStringMain {
    public static void main(String[] args) {
        Car car = new Car();
        Cat cat = new Cat("icefire");
        Object obj = new Object();

        System.out.println("== 정적 메소드로 객체 정보출력하기 ==");
        ObjectPrint.getInfo(car);
        ObjectPrint.getInfo(cat);
        ObjectPrint.getInfo(obj);
    }
}

출력 결과

== 정적 메소드로 객체 정보출력하기 ==
(Debug) 객체 정보: object.toString.Car@4e50df2e
(Debug) 객체 정보: Cat Name: icefire
(Debug) 객체 정보: java.lang.Object@34a245ab

ObjectPrint 클래스의 getInfo 메소드로 Object 타입은 모든 참조를 할당할 수 있다. 이러한 특성은 다형적 참조이며 결과를 보듯이 모든 객체가 인자로 받아 정보를 출력한다.

그림으로 보기

Cat cat = new Cat();
ObjectPrint.getInfo(cat);
  • Object obj 인수로 Cat 타입의 참조 값이 전달되었다.
  • 메소드 내부에서는 obj.toString() 호출한다.
  • 자신의 인스턴스 내부 Object 타입부터 toString() 찾는다
    • Object 부터 살피는 이유는 obj 변수가 Object 타입이다.
  • 찾아낸 toString() 메소드를 호출하지 않고,
    자식 클래스에서 오버라이딩이 있는지 찾는다.
  • Cat 타입에서 toString() 메소드 오버라이딩이 있다.
  • Cat.toString() 메소드를 호출한다.

Cat.toString() 호출 결과

(Debug) 객체 정보: Cat Name: icefire

toString() 참조 값을 출력하고 싶다면?

앞서 만든 코드에서 toString(), hashcode() 메소드를 재정의하면 참조 값을 출력하지 않는다는 것을 볼 수 있었다.

참조 값을 출력하는 방법은 toString() 소스 내부와 참고하여 작성하면 된다.

Cat cat = new Cat();
String ref = Integer.toHexString(System.identityHashCode(cat));
System.in.println("My Memory Reference Value: " + ref);

출력 결과

My Memory Reference Value: 7cc355be