만약 Object 가 없고, 또 Object 가 제공하는 toString() 이 없다면 서로 아무 관계가 없는 객체의 정보를 출력하기 어려울 것이다. 여기서 아무 관계가 없다는 것은 공통의 부모가 없다는 뜻이다. 아마도 다음의 BadObjectPrinter 클래스와 같이 각각의 클래스마다 별도의 메서드를 작성해야 할 것이다.
public class BadObjectPrinter {
public static void print(Car car) { //Car 전용 메서드
String string = "객체 정보 출력: " + car.carInfo(); //carInfo() 메서드 만듬
System.out.println(string);
}
public static void print(Dog dog) { //Dog 전용 메서드
String string = "객체 정보 출력: " + dog.dogInfo(); //dogInfo() 메서드 만듬
System.out.println(string);
}
}
BadObjectPrinter 는 구체적인 타입인 Car , Dog 를 사용한다. 따라서 이후에 출력해야 할 구체적인 클래스가 10개로 늘어나면 구체적인 클래스에 맞추어 메서드도 10개로 계속 늘어나게 된다. 이렇게 BadObjectPrinter 클래스
가 구체적인 특정 클래스인 Car , Dog 를 사용하는 것을 BadObjectPrinter 는 Car , Dog 에 의존한다고 표현한다.다행히도 자바에는 객체의 정보를 사용할 때, 다형적 참조 문제를 해결해줄 Object 클래스와 메서드 오버라이딩 문제를 해결해줄 Object.toString() 메서드가 있다. (물론 직접 Object 와 비슷한 공통의 부모 클래스를 만들어서 해결할 수도 있다.)
ObjectPrinter 클래스는 Car , Dog 같은 구체적인 클래스를 사용하는 것이 아니라, 추상적인 Object 클래스를 사용한다. 이렇게 ObjectPrinter 클래스가 Object 클래스를 사용하는 것을
ObjectPrinter 클래스가 Object 에 클래스에 의존한다고 표현한다. public class ObjectPrinter {
public static void print(Object obj) {
String string = "객체 정보 출력: " + obj.toString();
System.out.println(string);
}
}
ObjectPrinter 는 구체적인 것에 의존하는 것이 아니라 추상적인 것에 의존한다.
추상적: 여기서 말하는 추상적이라는 뜻은 단순히 추상 클래스나 인터페이스만 뜻하는 것은 아니다.
Animal과Dog,Cat의 관계를 떠올려보자.Animal같은 부모 타입으로 올라갈수록 개념은 더 추상적이게 되고,Dog,Cat과 같이 하위 타입으로 내려갈 수록 개념은 더 구체적이게 된다.

ObjectPrinter 와 Object 를 사용하는 구조는 다형성을 매우 잘 활용하고 있다. 다형성을 잘 활용한다는 것은 다형적 참조와 메서드 오버라이딩을 적절하게 사용한다는 뜻이다.
ObjectPrinter 의 print() 메서드와 전체 구조를 분석해보자.
print(Object obj) , Object 타입을 매개변수로 사용해서 다형적 참조를 사용한다. Car , Dog 인스턴스를 포함한 세상의 모든 객체 인스턴스를 인수로 받을 수 있다.Object 는 모든 클래스의 부모이다. 따라서 Dog , Car와 같은 구체적인 클래스는 Object 가 가지고 있는 toString() 메서드를 오버라이딩 할 수 있다. 따라서 print(Object obj) 메서드는 Dog , Car 와 같은 구체적인 타입에 의존(사용)하지 않고, 추상적인 Object 타입에 의존하면서 런타임에 각 인스턴스의 toString() 을 호출할 수 있다.OCP 원칙 기본편에서 학습한 OCP 원칙을 떠올려보자.
toString() 을 오버라이딩해서 기능을 확장할 수 있다.Object 와 toString() 을 사용하는 클라이언트 코드인 ObjectPrinter 는 변경하지 않아도 된다.