[Java] 김영한의 실전 자바 - 중급편 섹션1 Object 클래스
○ java.lang 패키지 소개
◼️java.lang 패키지 대표적 클래스들
- Object: 모든 자바 객체 부모 클래스
- String: 문자열
- Integer, Long, Double: 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것
- Class: 클래스 메타 정보
- System: 시스템과 관련된 기본 기능들을 제공
java.lang 패키지는 모든 자바 애플리케이션에 자동으로 임포트(import)된다.
○ Object 클래스
자바에서 모든 객체의 최종 부모는 Object이다.
클래스에 상속 받을 부모 클래스가 없으면 묵시적으로 Object 클래스를 상속받는다. (extends Object)
묵시적(Implicit) vs 명시적(Explicit)
묵시적: 개발자가 코드에 직접 기술하지 않아도 시스템 또는 컴파일러에 의해 자동으로 수행되는 것을 의미
명시적: 개발자가 코드에 직접 기술해서 작동하는 것을 의미
자바에서 Object 클래스가 최상위 부모 클래스인 이유
모든 클래스가 `Object` 클래스를 상속 받는 이유는 다음과 같다.
- 공통 기능 제공 : ex_toString(),equals(),getClass()...
- 다형성의 기본 구현: Object는 모든 객체를 다 담을 수 있다. 타입이 다른 객체들을 여기에 보관 가능하다.
간단한 오브젝트 메서드 활용예시를보자. toString()은 객체의 정보를 제공한다.
package lang.object;
public class ObjectMain {
public static void main(String[] args) {
Child child = new Child();
child.childMethod();
child.parentMethod();
// toString()은 Object 클래스의 메서드
String string = child.toString();
System.out.println(string);
}
}
Child.childMethod
Parent.parentMethod
lang.object.Child@X001
1. child.toString()을 호출한다.
2. 먼저 본인의 타입인 `Child` 에서 `toString()` 을 찾는다. 없으므로 부모 타입으로 올라가서 찾는다.
3. 부모 타입인 `Parent` 에서 찾는다. 없으므로 부모 타입으로 올라가서 찾는다.
4. 부모 타입인 `Object` 에서 찾는다. `Object` 에 `toString()` 이 있으므로 이 메서드를 호출한다.
java.lang 패키지는 모든 자바 애플리케이션에 자동으로 임포트(import)된다.
○ Object 다형성
Object 다형성을 이용한 아래 코드를 보자.
package lang.object.poly;
class Dog {
public void sound() {
System.out.println("멍멍");
}
}
public class ObjectPolyExample1 {
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에서 obj.sound()를 호출하면 오류가 발생한다.왜냐하면 obj는 Object타입이기 때문에, sound() 메서드가 없다.
따라서 다운 캐스팅 후 sound()를 찾아 호출한다.
◼️ Object를 활용한 다형성의 한계
- `Object` 는 모든 객체의 부모이므로 모든 객체를 담을 수 있다.
- `Object` 를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다. `Object` 가 세상의 모든 메서드를 알고 있는 것이 아니다.
○ Object 배열
package lang.object.poly;
public class ObjectPolyExample2 {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
Object object = new Object(); // Object 인스턴스도 만들 수 있다.
Object[] objects = {dog, car, object};
size(objects);
}
private static void size(Object[] objects) {
System.out.println("전달된 객체의 수는: " + objects.length);
}
}
Object타입의 배열은 모든 객체를 담을 수 있기 때문에, 클래스가 추가 혹은 변경되어도 메서드 수정이 필요치 않다.
만약 Object가 없다면 모든 객체를 받을 수 있는 메서드를 만들 수 없고, 모든 객체를 저장할 수 있는 배열을 만들 수 없다.
○ toString()
기본적으로 패키지를 포함한 객체의 이름과 객체의 참조값(해시 코드)를 16진수로 제공한다.
객체를 바로 전달하면 객체의 정보를 출력한다.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
보통은 toString()을 재정의(오버라이딩)해서 보다 유용한 정보를 제공하는 것이 일반적이다.
package lang.object.tostring;
public class Car {
private String carName;
public Car(String carName) {
this.carName = carName;
}
}
package lang.object.tostring;
public class Dog {
private String dogName;
private int age;
public Dog(String dogName, int age) {
this.dogName = dogName;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"dogName='" + dogName + '\'' +
", age=" + age +
'}';
}
}
public class ObjectPrinter {
public static void print(Object obj) {
String string = "객체 정보 출력: " + obj.toString();
System.out.println(string);
}
}
package lang.object.tostring;
public class ToStringMain2 {
public static void main(String[] args) {
Car car = new Car("ModelY");
Dog dog1 = new Dog("멍멍이1", 2);
Dog dog2 = new Dog("멍멍이2", 5);
System.out.println("1. 단순 toString 호출");
System.out.println(car.toString());
System.out.println(dog1.toString());
System.out.println(dog2.toString());
System.out.println("2. println 내부에서 toString 호출");
// println 내부에서 toString 호출
System.out.println(car);
System.out.println(dog1);
System.out.println(dog2);
System.out.println("3. Object 다형성 활용");
ObjectPrinter.print(car);
ObjectPrinter.print(dog1);
ObjectPrinter.print(dog2);
}
}
1. 단순 toString 호출
lang.object.tostring.Car@452b3a41
Dog{dogName='멍멍이1', age=2}
Dog{dogName='멍멍이2', age=5}
2. println 내부에서 toString 호출
lang.object.tostring.Car@452b3a41
Dog{dogName='멍멍이1', age=2}
Dog{dogName='멍멍이2', age=5}
3. Object 다형성 활용
객체 정보 출력: lang.object.tostring.Car@452b3a41
객체 정보 출력: Dog{dogName='멍멍이1', age=2}
객체 정보 출력: Dog{dogName='멍멍이2', age=5}
Car인스턴스는 toString() 을 재정의 하지 않았다. 따라서 기본 toString() 메서드를 사용한다. Dog인스턴스는 toString() 을 재정의 한 덕분에 객체의 상태를 명확하게 확인할 수 있다.
- ObjectPrinter를보면, Object obj인수로 dog(Dog) 가 전달 된다. 이때 메서드 내부에서 obj.toString()을 호출한다.
- obj는 Object타입이다. 따라서 Object 에 있는 toString()을 찾는다.
- 이때 자식에 재정의(오버라이딩)된 메서드가 있는지 찾아본다. Dog에 재정의된 메서드가 있다. Dog.toString() 을 실행한다.
○ Object와 OCP
Object와 제공 메서드 toString()이 없다고 생각해보자. 그러면 아래처럼 각 객체(Car,Dog...)별로 메서드를 작성해야 한다.
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);
}
}
위의 문제점은 구체적인 것에 의존한다는 것이다. 다행히도 자바에는 객체의 정보를 사용할 때, 다형적 참조 문제를 해결해줄 Object 클래스와 메서드 오버라이딩 문제 를 해결해줄 Object.toString() 메서드가 있다.
아래처럼 추상적인 것에 의존하도록 위 코드를 변경할 수 있다.
public class ObjectPrinter {
public static void print(Object obj) {
String string = "객체 정보 출력: " + obj.toString();
System.out.println(string);
}
}
다형적 참조: 다형적 참조를 사용해, 모든 객체 인스턴스를 인수로 받는다.
메서드 오버라이딩: 구체적 클래스는 Object가 가지고있는 toString()을 오버라이딩할 수 있다.
이는 OCP원칙을 만족한다.
Open: 새로운 클래스를 추가하고, toString() 을 오버라이딩해서 기능을 확장할 수 있다.
Closed: 새로운 클래스를 추가해도 Object와 toString()을 사용하는 클라이언트 코드인 ObjectPrinter는 변경하지 않아도 된다.
즉 정리해보면, 클라이언트 코드가 구체 클래스(Car,Dog)에 의존하지않고 Object에 의존하면서 OCP 원칙을 지킬 수 있었다.
◼️ System.out.println()
- 위의 ObjectPrinter.print()는 System.out.println()의 작동방식이다.
- System.out.println()도 Object를 사용하고 내부에서 toString()을 호출한다.
- 따라서 이를통해 세상의 모든 객체의 정보( toString() )를 편리하게 출력할 수 있다.
○ equals() - 1. 동일성과 동등성
동일성(Identity): == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
동등성(Equality): equals() 메서드를 사용하여 두 객체가 논리적으로 동등한지 확인
public boolean equals(Object obj) {
return (this == obj);
}
Object가 기본으로 제공하는 equals()는 == 으로 동일성 비교를 제공한다.따라서 동등성 비교를 사용하고 싶으면 equals() 메서드를 재정의해야 한다. 그렇지 않으면 Object 는 동일성 비교를 기본으로 제공한다.
○ equals() - 2. 구현
id가 같으면 논리적으로 같은 객체로 정의해보자.
package lang.object.equals;
public class UserV2 {
private String id;
public UserV2(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
UserV2 user = (UserV2) obj; // 객체 특정 값 사용위한 다운캐스팅
return id.equals(user.id); // id(고객번호) 동등성 비교
}
}
package lang.object.equals;
public class EqualsMainV2 {
public static void main(String[] args) {
UserV2 user1 = new UserV2("id-100");
UserV2 user2 = new UserV2("id-100");
System.out.println("identity = " + (user1 == user2));
System.out.println("equality = " + user1.equals(user2));
}
}
identity = false
equality = true
◼️ 정확한 equals() 구현
실제 정확한 동작을하려면 아래 규칙을 지켜야한다. 대부분 IDE는 정확한 equals()코드를 자동으로 만들어준다.
equals() 메서드를 구현할 때 지켜야 하는 규칙
- 반사성(Reflexivity): 객체는 자기 자신과 동등해야 한다. ( x.equals(x)는 항상 true ).
- 대칭성(Symmetry): 두 객체가 서로에 대해 동일하다고 판단하면, 이는 양방향으로 동일해야 한다. ( x.equals(y) 가 true이면 y.equals(x)도 true).
- 추이성(Transitivity): 만약 한 객체가 두 번째 객체와 동일하고, 두 번째 객체가 세 번째 객체와 동일하다면, 첫 번째 객체는 세 번째 객체와도 동일해야 한다.
- 일관성(Consistency): 두 객체의 상태가 변경되지 않는 한, equals()메소드는 항상 동일한 값을 반환해야 한다.
- null에 대한 비교: 모든 객체는 null과 비교했을 때 false 를 반환해야 한다.
package lang.object.equals;
public class UserV2 {
private String id;
public UserV2(String id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) { // 객체 자체 비교 (reference equality)
return true;
}
if (obj == null || getClass() != obj.getClass()) { // null 체크 및 타입 검사
return false;
}
UserV2 user = (UserV2) obj; // 캐스팅
return id.equals(user.id); // id 필드 비교 (value equality)
}
}
(실무에서는 대부분 IDE가 만들어주는 `equals()` 를 사용하므로, 이 규칙을 외우기 보다는 대략 이렇구나 정도로 한번 읽어보고 넘어가면 충분하다!)
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 중급편 섹션3,4 String ,래퍼,Class 클래스 (0) | 2024.05.09 |
---|---|
[Java] 김영한의 실전 자바 - 중급편 섹션2 불변 객체 (0) | 2024.05.05 |
[Java의 정석] chapter09 java.lang패키지와 유용한 클래스 - 요약정리 (0) | 2024.05.05 |
[Java] 김영한의 실전 자바 - 기본편 섹션9,10 상속,다형성 (0) | 2024.04.24 |
[Java] 김영한의 실전 자바 - 기본편 섹션7,8 자바 메모리 구조와 static, final (0) | 2024.04.18 |