자바가 기본으로 제공하는 라이브러리 중에 가장 기본이 되는 것이 바로 java.lang
패키지이다.
여기서 lang
은 Language(언어)
의 줄임말이다. 쉽게 이야기해서 자바 언어를 이루는 가장 기본이 되는 클래스들을 보관하는 패키지를 뜻한다.
Object
: 모든 자바 객체의 부모 클래스String
: 문자열Integer
, Long
, Double
: 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것Class
: 클래스 메타 정보System
: 시스템과 관련된 기본 기능 제공toString()
equals()
getClass()
Object
는 모든 클래스의 부모 클래스이다. 따라서 모든 객체를 참조할 수 있다.Object
클래스는 다형성을 지원하는 기본적인 메커니즘을 제공한다.Object
타입으로 처리될 수 있으며 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 해준다.public class ObjectPolyMain {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
action(dog);
action(car);
}
private static void action(Object obj) {
if (obj instanceof Dog) {
Dog dog = (Dog) obj;
dog.sound();
}
if (obj instanceof Car) {
Car car = (Car) obj;
car.move();
}
}
}
action()
메서드 안에서 obj.sound()
를 호출하면 오류가 발생한다. 왜냐하면 매개변수 obj
는 Object
타입이기 때문이다. Object
에는 sound()
메서드가 없다.Object
를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다.Object
는 모든 타입의 객체를 담을 수 있다. 따라서 Object[]
을 만들면 세상의 모든 객체를 담을 수 있는 배열을 만들 수 있다.public class ObjectPolyMain2 {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
Object object = new Object();
Object[] objects = {dog, car, object};
size(objects);
}
private static void size(Object[] objects) {
System.out.println("전달된 객체의 수는: " + objects.length);
}
}
Object.toString()
메서드는 객체의 정보를 문자열 형태로 제공한다.Object
클래스에 정의되므로 모든 클래스에서 상속받아 사용할 수 있다.Object
가 제공하는 toString()
메서드는 기본적으로 패키지를 포함한 객체의 이름과 객체의 참조값(해시코드)를 16진수로 제공한다.hashCode()
)에 대한 정확한 내용은 이후에 별도로 다룬다. 지금은 객체의 참조값 정도로 생각하면 된다.println()
과 toString()
의 결과를 출력한 코드는 서로 같다. public class ToStringMain {
public static void main(String[] args) {
Object object = new Object();
String string = object.toString();
System.out.println(string); // java.lang.Object@10f87f48
System.out.println(object); // java.lang.Object@10f87f48
}
}
Object.toString()
메서드가 클래스 정보와 참조값을 제공하지만 이 정보만으로는 객체의 상태를 적절히 나타내지 못한다. 그래서 보통 toString()
을 재정의(오버라이딩)해서 보다 유용한 정보를 제공하는 것이 일반적이다.package me.jangwoojin.object.tostring;
public class ToStringMain {
public static void main(String[] args) {
Dog dog = new Dog("dogName", 10);
Car car = new Car("carName");
System.out.println(dog);
System.out.println(car);
}
}
public class BadObjectPrinter {
public static void print(Car car) {
String string = "객체 정보 출력: " + car.carInfo();
System.out.println(string);
}
public static void print(Dog dog) {
String string = "객체 정보 출력: " + dog.dogInfo();
System.out.println(string);
}
}
BadObjectPrinter
는 구체적인 타입인 Car
, Dog
를 사용한다. 따라서 이후에 출력해야 할 구체적인 클래스가 10개로 늘어나면 구체적인 클래스에 맞추어 메서드도 10개로 계속 늘어난다. 이렇게 BadObjectPrinter
클래스가 구체적인 특정 클래스인 Car
, Dog
를 사용하는 것을 BadObjectPrinter
는 Car
, Dog
에 의존한다고 표현한다.public class ObjectPrinter {
public static void print(Object obj) {
String string = "객체 정보 출력: " + obj.toString();
System.out.println(string);
}
}
ObjectPrinter
클래스는 Car
, Dog
같은 구체적인 클래스를 사용하는 것이 아니라 추상적인 Object
클래스를 사용한다. 이렇게 ObjectPrinter
클래스가 Object
클래스를 사용하는 것을 Object
클래스에 의존한다고 표현한다.❗추상적 : 여기서 말하는 추상적이라는 뜻은 단순히 추상 클래스나 인터페이스만 뜻하는 것은 아니다.
Animal
과Dog
,Cat
의 관계를 떠올려보자.Animal
같은 부모 타입으로 올라갈수록 개념은 더 추상적이고Dog
,Cat
와 같이 하위 타입으로 내려갈수록 개념은 더 구체적이게 된다.
ObjectPrinter.print(Object obj)
에 인자로 어떤 객체가 전달 될지는 프로그램을 실행해봐야 알 수 있다. 어떤 경우에는 Car
인스턴스가 넘어오고, 어떤 경우에는 Dog
인스턴스가 넘어온다. 이렇게 런타임에 어떤 인스턴스를 사용하는지를 나타내는 것이 동적 의존관계이다.Object
는 동등성 비교를 위한 equals()
메서드를 제공한다.
자바는 두 객체가 같다라는 표현을 2가지로 분리해서 제공한다.
==
연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인 equals()
메서드를 사용하여 두 객체가 논리적으로 동등한지 확인쉽게 이야기해서 동일성은 물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인하는 것이고, 동등성은 논리적으로 같은지 확인하는 것이다. 동일성은 자바 머신 기준이고 메모리의 참조가 기준이므로 물리적이다. 반면 동등성은 보통 사람이 생각하는 논리적인 기준에 맞추어 비교한다.
public class EqualsMainV1 {
public static void main(String[] args) {
UserV1 userV1 = new UserV1("id-100");
UserV1 userV2 = new UserV1("id-100");
System.out.println("identity = " + (userV1 == userV2));
System.out.println("equality = " + (userV1.equals(userV2)));
}
}
public boolean equals(Object obj) {
return (this == obj); // ==으로 비교
}
public class UserV2 {
private String id;
public UserV2(String id) {
this.id = id;
}
// equals() 메서드 오버라이딩
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
UserV2 userV2 = (UserV2) object;
return Objects.equals(id, userV2.id);
}
// hashCode() 메서드 오버라이딩
@Override
public int hashCode() {
return Objects.hashCode(id);
}
}
동일성(Identity) : 객체의 참조가 다르므로 동일성은 다르다.
동등성(Equality) : user1, user2는 서로 다른 객체이지만 둘다 같은 id
를 가지고 있다. 따라서 동등하다.
✔️equals() 메서드를 구현할 때 지켜야 하는 규칙
equals()
메서드는 항상 동일한 값을 반환해야 한다.