김영한의 Java 중급편 강의(첫번째)를 듣고 정리한 내용

목표: java의 기능보다는 제공하는 기술들이 왜 필요한지 이해하는 것

섹션1. Object 클래스

java 언어를 이루는 기본적인 클래스들 패키지 java.lang
Object, String, Integer, Double, Math, System, Class 등

이 패키지는 모든 java 애플리케이션에서 자동으로 import 된다.

java.lang.Object 클래스는 모든 클래스의 조상이다. 즉, 모든 클래스는 Object 클래스를 기본적으로 상속받는다. 그래서 Object에 있는 toString(), equals(), hashCode() 메서드를 사용할 수 있다. 이 말은 늘 메모리에도 같이 존재한다는 뜻이다. 자식 인스턴스가 생성될 때는 부모도 같이 생성되니까..

그럼 이걸 왜 할까?

Object 다형성

Object는 모든 객체의 부모이므로 이걸 인자 타입으로 하면 어떤 객체든 받을 수 있다. 즉 모든 객체를 다형적 참조 가능

하지만 Object를 인수로 하면 전달된 객체의 구체적인 메서드를 호출할 수 없다. 왜냐하면 Object는 모든 객체의 부모이기 때문에 자식 클래스의 메서드를 알 수 없기 때문이다.
따라서 전달받은 객체를 호출하려면 각 객체에 맞게 다운캐스팅이 필요.

제대로 다형성 활용하려면 다형적 참조뿐 아니라 오버라이딩이 필요하므로 Object만 활용해서는 다형성에 한계가 있다.

그럼 Object는 언제 쓰는가?

Object 타입을 쓰면 어떤 객체든 사용할 수 있는 배열을 만들 수 있다. 배열 길이 같은, 타입이 상관 없는 그런 것들에 활용할 수 있다.

모든 것을 다 참조할 수 있는 공통 타입을 만들면 여러 부분에서 통일성도 있어짐.

Object.toString()

Object 클래스의 toString() 메서드는 객체를 문자열로 표현하는 메서드이고 Object에 있으니 모든 객체에서 사용 가능. 기본동작은 [클래스이름@해시코드] 형태. 해시코드는 지금은 참조값 정도로만 생각하자.

이때 System.out.println()을 사용하면 내부적으로 toString() 메서드가 자동으로 호출된다. 즉, System.out.println(object)System.out.println(object.toString())과 같다.

다만 보통은 toString() 메서드가 객체 상태를 적절히 나타내도록 오버라이딩해서 사용한다. 인텔리제이에서 Ctrl + N 단축키를 통하면 쉽게 만들 수도 있다. 물론 직접 제어해 줘야 할 때도 있겠지만..

System.out.println()은 내부적으로 toString을 호출하고, 이런 경우 오버라이딩된 메서드가 항상 우선이므로 toString을 오버라이딩했을 경우 println에도 적용된다.

toString이나 hashCode 오버라이딩했을 경우 참조값 출력이 안될수있다. 이경우 객체 참조값을 사용하고 싶으면 System.identityHashCode()를 사용하면 된다. 이렇게 하자.

Integer.toHexString(System.identityHashCode(object))

Object와 OCP

Object.toString()이 없다면 구체적인 클래스에서 정보를 표현하기 위해 사용하는 메서드가 늘어날 것이다. 따라서 Object를 만들어서 정보 표시가 추상적인 클래스에 의존하도록 한다.

다형성을 잘 활용함 : 다형적 참조와 메서드 오버라이딩을 활용한다는 뜻. 구체적인 클래스는 당연히 toString()을 오버라이딩해 사용 가능.

toString오버라이딩을 통해 확장은 가능하지만 기존 코드는 변경하지 않고도 새로운 기능을 추가할 수 있다. 즉, OCP를 잘 지키고 있다.

이걸 잘 쓰는 게 바로 System.out.println()이다. Object 타입을 인자로 받고 toString을 내부적으로 호출한다.

Java 언어 그 자체도 이렇게 객체지향(OCP)을 잘 활용하고 있다! 아무리 클래스를 추가하고 toString()을 오버라이딩해도 기존 코드는 변경하지 않고도 객체 정보를 출력할 수 있다.

정적 의존관계: 주로 클래스 간 관계로 컴파일 타임에 결정

동적 의존관계: 런타임에 확인 가능한 의존 관계. 어떤 객체가 전달될지는 런타임에 결정된다.

동일성과 동등성

동일성(identity) : == 연산자로 참조값이 같은지 확인하는 것
동등성(equality) : equals() 메서드를 통해 두 객체가 논리적으로 동등한지 확인

그런데 Object의 기본 equals()==를 사용한다. 그래서 우리가 직접 필요한 동등성을 오버라이딩해야 함.

이때 equals는 Object를 매개변수로 사용하기 때문에 객체의 특정 값을 사용하려면 다운캐스팅을 해줘야 한다.

하지만 사실 equals를 정확하게 구현하는 건 어렵다. equals는 reflexive, symmetric, transitive, consistent(두 객체 상태가 변경되지 않으면 항상 값 동일), 그리고 null이 아닌 객체와 비교할 때 false를 반환해야 한다.

단 이걸 외우기보단 대략 이렇구나 정도로 읽고 넘어가자. 실제로는 IDE가 만들어준걸 많이 쓴다. 또한 동일성 비교가 모든 클래스에 늘 필요한 건 아니다.

Object의 다른 메서드

clone: 객체 복사. 잘 사용은 안함
hashCode: 해시코드 반환. equals와 함께 쓸 때가 많다. 이후 컬렉션 프레임워크에서 다룸
getClass: 클래스 할 때 다룸
notify, notifyAll, wait: 멀티스레드에서 사용. 자바는 멀티스레드 지원이 기본으로 되어있다. 이건 나중에 다룸