< 김영한의 실전 자바 - 중급편 > 강의를 보고 이해한 내용을 바탕으로 합니다.
java.lang 패키지
자바가 기본으로 제공하는 라이브러리.
(자바 언어를 이루는 가장 기본이 되는 클래스들을 보관하는 패키지를 말한다.)
모든 자바 애플리케이션에 자동으로 임포트 된다.
java.lang 패키지의 대표적인 클래스
자바에서 모든 클래스의 최상위 부모 클래스.
부모가 없으면 묵시적으로 Object 클래스를 상속받는다. (자바가 extends Object
코드를 넣어준다.)
공통 기능을 제공받을 수 있고 다형성의 기본을 구현할 수 있기 때문이다.
객체의 정보를 제공, 이 객체가 다른 객체와 같은지 비교, 객체가 어떤 클래스로 만들어졌는지 확인하는 기능. --> 객체를 만들때마다 새로운 메서드를 만들기 번거롭다.
그리고 만들어도 개발자끼리 메서드 이름이 다르니 일관성이 없다.
Object는 모든 객체에 필요한 공통 기능을 제공한다. 최상위 부모 클래스이기 때문에 모든 객체는 공통 기능을 편리하게 상속 받을 수 있다.
=> 프로그래밍이 단순화되고 일관성을 가지게 된다.
Object는 모든 클래스의 부모 클래스이다. 따라서 모든 객체를 참조할 수 있다.
모든 자바 객체는 Object 타입으로 처리될 수 있으며 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 해준다.
Object는 모든 객체를 다 담을 수 있으므로 타입이 다른 객체들을 어딘가에 보관해야 한다면 Object에 보관하면 된다.
class Car {
public void move() {
System.out.println("자동차 이동");
}
}
class Dog {
public void sound() {
System.out.println("멍멍");
}
}
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
action(dog);
action(car);
}
private static void action(Object obj) {
//obj.sound(); //컴파일 오류, Object는 sound()가 없다.
//obj.move(); //컴파일 오류, Object는 move()가 없다.
//객체에 맞는 다운캐스팅 필요
if (obj instanceof Dog dog) {
dog.sound();
} else if (obj instanceof Car car) {
car.move();
}
}
Object는 모든 타입의 부모이므로 모든 객체를 담을 수 있다.
따라서 아래와 같이 코드를 변경할 수 있다.
Object dog = new Dog(); //Dog -> Object
Object car = new Car(); //Car -> Object
Object를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다.
다형성을 제대로 활용하려면 다형적 참조 + 메서드 오버라이딩
을 함께 사용해야 한다.
그러나 Object에는 다른 객체의 메서드가 정의되어 있지 않다. 따라서 메서드 오버라이딩을 활용할 수 없다. => 각 객체의 기능을 호출하려면 다운캐스팅을 해야한다.
즉, 다형적 참조는 가능하지만 메서드 오버라이딩이 안되기 때문에 다형성을 활용하기엔 한계가 있다.
그러면 object를 언제 활용하면 좋은가?
Object는 모든 타입의 객체를 담을 수 있기 때문에 Object배열(Object[])은 모든 객체를 담을 수 있는 배열을 만들 수 있다.
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
Object object = new Object(); //Object 인스턴스도 만들 수 있다.
Object[] objects = {dog, car, object};
}
Object[] objects = {dog, car, object};
//쉽게 풀어서 설명하면 다음과 같다.
Object objects[0] = new Dog();
Object objects[1] = new Car();
Object objects[2] = new Object();
위와 같은 Object 개념이 없다면 모든 객체를 저장할 수 있는 배열을 만들 수 없다.
Object.toString() 메서드는 객체의 정보를 문자열 형태로 제공한다.
-> 디버깅과 로깅에 유용하게 사용된다.
Object 클래스에 정의되어 있으므로 모든 클래스에서 상속받아 사용할 수 있다.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString() 메서드는 기본적으로 패키지를 포함한 객체의 이름과 객체의 참조값(해시코드)를 16진수로 제공한다. java.lang.Object@a09ee92
public static void main(String[] args) {
Object object = new Object();
String string = object.toString();
//toString() 반환값 출력
System.out.println(string); //java.lang.Object@a09ee92
//object 직접 출력
System.out.println(object); //java.lang.Object@a09ee92
}
toString()의 결과를 출력한 코드와 object를 println()에 직접 출력한 코드의 결과는 완전히 같다.
System.out.println()
메서드가 내부에서 toString()을 호출하기 때문이다.
Object.toString() 메서드가 클래스 정보와 참조값을 제공하지만 이 정보만으로는 객체의 상태를 적절히 나타내기 어렵다. 그래서 보통 toString()을 재정의(오버라이딩)해서 보다 유용한 정보를 제공한다. (IDE 도움을 받아서 작성하면 편하다.)
public Dog(String dogName, int age) {
this.dogName = dogName;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"dogName='" + dogName + '\'' +
", age=" + age +
'}';
}
toString()은 기본적으로 객체의 참조값을 출력하는데 이를 재정의하면 객체의 참조값을 출력할 수 없다. 다음과 같은 방법으로 객체의 참조값을 출력할 수 있다.
String refValue = Integer.toHexString(System.identityHashCode(dog1));
System.out.println("refValue = " + refValue);
만약 Object가 없고 Object가 제공하는 toString()이 없으면 서로 아무 관계 없는(공통의 부모가 없는) 객체의 정보를 출력하기 어려울 것이다. => 각각의 클래스마다 별도의 메서드를 작성해야 할 것이다.
즉, 구체적인 것에 의존하게 된다.
Object가 있기 때문에 추상적인 것(부모타입일수록 추상. 하위타입일수록 구체)에 의존하게 되고, 다형성을 잘 활용하게 된다.
(다형성을 잘 활용한다 = 다형적 참조 + 메서드 오버라이딩)
=> 다형적 참조, 메서드 오버라이딩, 크라이언트 코드가 구체에 의존하는 것이 아니라 추상인 Object에 의존하면서 OCP원칙을 지킬 수 있었다.
System.out.println() 메서드도 Object 매개변수를 사용하고 내부에서 toString()을 호출하기 때문에 모든 객체의 정보(toString())를 편리하게 출력할 수 있다.
Object는 동등성 비교를 위한 equals() 메서드를 제공한다.
자바는 두 객체가 같다라는 표현을 두가지로 분리해서 제공한다.
==
. 두 객체의 참조가 동일한 객체를 가리키는지 (물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인)equals()
. 두 객체가 논리적으로 동등한지User a = new User("id-100") //참조 x001
User b = new User("id-100") //참조 x002
물리적으로 다른 메모리에 있는 객체이지만 논리적으로 같다. => 동일성은 다르나 동등성은 같다.
Object가 기본으로 제공하는 equals()는 ==으로 동일성 비교를 제공한다.
public boolean equals(Object obj) {
return (this == obj);
}
동등성이라는 개념은 각각의 클래스마다 다르다.
어떤 클래스는 주민등록번호 기반으로, 어떤 클래스는 고객 연락처 기반으로 등등.
따라서 동등성 비교를 사용하려면 equals() 메서드를 재정의해야한다.
그렇지 않으면 Object는 동일성 비교를 기본으로 제공한다.
IDE의 generator 단축키로 equals() 메서드를 구현하자.
(동등성 비교가 항상 필요한 것은 아니고 동등성 비교가 필요한 경우에만 equals()를 재정의하자.)