1. Object 클래스
java.lang 패키지
자바가 기본으로 제공하는 라이브러리(클래스 모음) 중에 가장 기본이 되는 것이 바로 java.lang
패키지이다. 여기서 lang
은 Language
(언어)의 줄임말이다. 쉽게 이야기해서 자바 언어를 이루는 가장 기본이 되는 클래스들을 보관하는 패키지를 뜻한다.
java.lang
패키지의 대표적인 클래스들
java.lang
패키지의 대표적인 클래스들Object
: 모든 자바 객체의 부모 클래스String
: 문자열Integer
,Long
,Double
: 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것Class
: 클래스 메타 정보System
: 시스템과 관련된 기본 기능들을 제공
Object 클래스
자바에서 모든 클래스의 최상위 부모 클래스는 항상 Object
클래스이다.
클래스를 만들 떄 상속 받을 부모 클래스가 없다면 묵시적으로 Object
클래스를 상속 받은 것으로 생각하면 된다.
쉽게 생각해서 자바가
extends Object
코드를 넣어준다고 생각하면 된다.따라서
extends Object
는 생략하는 것을 권장한다.
묵시적(Implicit) vs 명시적(Explicit)묵시적 : 개발자가 코드에 직접 기술하지 않아도 시스템 또는 컴파일러에 의해서 자동으로 수행되는 것을 의미
명시적 : 개발자가 코드에 직접 기술해서 작동하는 것을 의미
Java 에서 Object 클래스가 최상위 부모 클래스인 이유
모든 클래스가 Object
클래스를 상속 받는 이유는 다음과 같다.
공통 기능 제공
다형성의 기본 구현
공통 기능 제공
객체의 정보를 제공하고, 이 객체가 다른 객체와 같은지 비교하고, 객체가 어떤 클래스로 만들어졌는지 확인하는 기능은 모든 객체에게 필요한 기본 기능이다. 이런 기능을 객체를 만들 때 마다 항상 새로운 메서드를 정의해서 만들어야 한다면 상당히 번거로울 것이다.
그리고 막상 만든다고 하더라도 개발자마다 서로 다른 이름의 메서드를 만들어서 일관성이 없을 것이다.
Object
클래스는 모든 객체에 필요한 공통 기능을 제공한다. Object
는 최상위 부모 클래스이기 때문에 모든 객체는 공통 기능을 편리하게 상속 받을 수 있다.
Object
가 제공하는 기능은 다음과 같다.
toString()
: 객체의 정보 제공equals()
: 같음을 비교getClass()
: 객체의 클래스 정보를 제공기타 여러가지 기능 ..
다형성의 기본 구현
부모는 자식을 담을 수 있다. Object
는 모든 클래스의 부모 클래스이다. 따라서 모든 객체를 참조할 수 있다.
Object
클래스는 다형성을 지원하는 기본적인 매커니즘을 제공한다. 모든 자바 객체는 Object
타입으로 처리될 수 있으며, 이는 다양한 타입의 객체를 통합적으로 처리할 수 있도록 해준다.
쉽게 이야기해서 Object
는 모든 객체를 다 담을 수 있다. 타입이 다른 객체들을 어딘가에 보관해야 한다면 바로 Object
에 보관하면 된다.
Object 를 활용한 다형성의 한계
Object
는 모든 객체를 대상으로 다형적 참조를 할 수 있다.쉽게 이야기해서
Object
는 모든 객체의 부모이므로 모든 객체를 담을 수 있다.
Object
를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다.Object
가 세상의 모든 메서드를 알고 있는 것은 아니다.
다형성을 제대로 활용하려면 다형적 참조 + 메서드 오버라이딩을 함께 사용해야 한다. 그런면에선 Object
를 사용한 다형성에는 한계(다운캐스팅을 해야만 한다) 가 있다.
Object
는 모든 객체의 부모이므로 모든 객체를 대상으로 참조를 할 수 있다. 하지만 Object
에는 Dog.sound()
, Car.move()
와 같은 다른 객체의 메서드가 정의되어 있지 않다. 따라서 메서드 오버라이딩을 활용 할 수 없다.
결국 각 객체의 기능을 호출하려면 다운캐스팅을 사용해야 한다.
결국, 다형적 참조는 가능하지만 메서드 오버라이딩이 안되기 때문에, 다형성을 활용하기에는 한계가 있다.
Object 배열
Object
는 모든 객체의 타입을 담을 수 있다. 따라서 Object[]
을 만들면 세상의 모든 객체를 담을 수 있는 배열을 만들 수 있다.
Object
타입을 사용한 덕분에 세상의 모든 객체를 담을 수 있는 배열을 만들 수 있었다.
size() 메서드
size(Object[] objects)
메서드는 배열에 담긴 객체의 수를 세는 역할을 담당한다.이 메서드는
Object
타입만 사용한다.Object
타입의 배열은 세상의 모든 객체를 담을 수 있기 때문에, 새로운 클 래스가 추가되거나 변경되어도 이 메서드를 수정하지 않아도 된다. 지금 만든size()
메서드는 자바를 사용하는 곳이 라면 어디든지 사용될 수 있다.
Object 가 없다면?
만약 Object
와 같은 개념이 없다면 어떻게 될까?
void action(Object obj)
과 같이 모든 객체를 받을 수 있는 메서드를 만들 수 없다.Object[] objects
처럼 모든 객체를 저장할 수 있는 배열을 만들 수 없다.
물론 Object
가 없어도 직접 MyObject
와 같은 클래스를 만들고 모든 클래스에서 직접 정의한 MyObject
를 상속 받으면 된다. 하지만 하나의 프로젝트를 넘어서 전세계 모든 개발자가 비슷한 클래스를 만들 것이고, 서로 호환되지 않는 수 많은 XxxObject
들이 넘쳐날 것이다.
Object 와 OCP
만약 Object
가 없고, 또 Object
가 제공하는 toString()
이 없다면 서로 아무 관계가 없는 객체의 정보를 출력하 기 어려울 것이다. 여기서 아무 관계가 없다는 것은 공통의 부모가 없다는 뜻이다. 아마도 다음의 BadObjectPrinter
클래스와 같이 각각의 클래스마다 별도의 메서드를 작성해야 할 것이다.
구체적인 것에 의존
BadObjectPrinter
는 구체적인 타입인 Car
, Dog
를 사용한다. 따라서 이후에 출력해야 할 구체적인 클래스가 10개로 늘어나면 구체적인 클래스에 맞추어 메서드도 10개로 계속 늘어나게 된다. 이렇게 BadObjectPrinter
클래스 가 구체적인 특정 클래스인 Car
, Dog
를 사용하는 것을 BadObjectPrinter
는 Car
, Dog
에 의존한다고 표현한 다.
BadObjectPrinter
는 구체적인 타입인 Car
, Dog
를 사용한다. 따라서 이후에 출력해야 할 구체적인 클래스가 10개로 늘어나면 구체적인 클래스에 맞추어 메서드도 10개로 계속 늘어나게 된다. 이렇게 BadObjectPrinter
클래스 가 구체적인 특정 클래스인 Car
, Dog
를 사용하는 것을 BadObjectPrinter
는 Car
, Dog
에 의존한다고 표현한 다.다행히도 자바에는 객체의 정보를 사용할 때, 다형적 참조 문제를 해결해줄 Object
클래스와 메서드 오버라이딩 문제 를 해결해줄 Object.toString()
메서드가 있다. (물론 직접 Object
와 비슷한 공통의 부모 클래스를 만들어서 해결할 수도 있다.)
추상적인 것에 의존 (OOP 를 사용하면 추상적인 것에 의존하자)
우리가 앞서 만든 ObjectPrinter
클래스는 Car
, Dog
같은 구체적인 클래스를 사용하는 것이 아니라, 추상적인Object
클래스를 사용한다. 이렇게 ObjectPrinter
클래스가 Object
클래스를 사용하는 것을ObjectPrinter
클래스가 Object
에 클래스에 의존한다고 표현한다.
ObjectPrinter
클래스는 Car
, Dog
같은 구체적인 클래스를 사용하는 것이 아니라, 추상적인Object
클래스를 사용한다. 이렇게 ObjectPrinter
클래스가 Object
클래스를 사용하는 것을ObjectPrinter
클래스가 Object
에 클래스에 의존한다고 표현한다.ObjectPrinter
와 Object
를 사용하는 구조는 다형성을 매우 잘 활용하고 있다. 다형성을 잘 활용한다는 것은 다형적 참조와 메서드 오버라이딩을 적절하게 사용한다는 것이다.
ObjectPrinter
의 print()
메서드와 전체 구조를 분석해보자.
다형적 참조:
print(Object obj)
,Object
타입을 매개변수로 사용해서 다형적 참조를 사용한다.Car
,Dog
인스턴스를 포함한 세상의 모든 객체 인스턴스를 인수로 받을 수 있다.메서드 오버라이딩:
Object
는 모든 클래스의 부모이다. 따라서Dog
,Car
와 같은 구체적인 클래스는Object
가 가지고 있는toString()
메서드를 오버라이딩 할 수 있다. 따라서print(Object obj)
메서 드는Dog
,Car
와 같은 구체적인 타입에 의존(사용)하지 않고, 추상적인Object
타입에 의존하면서 런타임에 각 인스턴스의toString()
을 호출할 수 있다.
OCP 원칙
기본편에서 학습한 OCP 원칙을 떠올려보자.
Open : 새로운 클래스를 추가하고,
toString()
을 오버라이딩해서 기능을 확장할 수 있다.Closed : 새로운 클래스를 추가해도
Object
와toString()
을 사용하는 클라이언트 코드인ObjectPrinter
는 변경하지 않아도 된다.
equals()
Object
에서는 동등성 비교를 위한 equals()
메서드를 제공한다.
자바는 두 객체가 같다라는 표현을 2가지로 분리해서 제공한다.
동일성(Identity) :
==
연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인동등성(Equality) :
equals()
메서드를 사용해서 두 객체가 논리적으로 동등한지 확인
단어 정리
"동일" 은 완전히 같음을 의미한다. 반면 "동등" 은 같은 가치나 수준을 의미하지만 그 형태나 외관이 같지는 않을 수 있다.
쉽게 이야기해서 동일성은 물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인하는 것이고, 동등성은 논리적은로 같은지를 확인하는 것이다.
동일성을 자바 머신 기준이고 메모리의 참조가 기준이므로 물리적이다. 반면 동등성은 보통 사람이 생각하는 논리적인 기준에 맞추어서 비교한다.
equals()
메서드를 구현할 때 지켜야 하는 규칙
equals()
메서드를 구현할 때 지켜야 하는 규칙반사성(Reflexivity): 객체는 자기 자신과 동등해야 한다. ( x.equals(x)
는 항상 true
).
대칭성(Symmetry): 두 객체가 서로에 대해 동일하다고 판단하면, 이는 양방향으로 동일해야 한다. (x.equals(y)
가 true
이면 y.equals(x)
도 true
).
추이성(Transitivity): 만약 한 객체가 두 번째 객체와 동일하고, 두 번째 객체가 세 번째 객체와 동일하다면, 첫 번째 객체는 세 번째 객체와도 동일해야 한다.
일관성(Consistency): 두 객체의 상태가 변경되지 않는 한, equals()
메소드는 항상 동일한 값을 반환해야 한다.
null에 대한 비교: 모든 객체는 null
과 비교했을 때 false
를 반환해야 한다.
실무에서는 대부분 IDE가 만들어주는 equals()
를 사용하므로, 이 규칙을 외우기 보다는 대략 이렇구나 정도로 한번 읽어보고 넘어가면 충분하다.
정리
참고로 동등성 비교가 항상 필요한 것은 아니다. 동등성 비교가 필요한 경우에만
equals()
를 재정의하면 된다.equals()
와hashCode()
는 보통 함께 사용된다. 이 부분은 뒤에 컬렉션 프레임워크에서 자세히 설명한다.
Last updated