[김영한의 실전 자바 - 중급 1편] 01. Object 클래스

Turtle·2024년 7월 4일
0
post-thumbnail

🙄java.lang 패키지 소개

자바가 기본으로 제공하는 라이브러리 중에 가장 기본이 되는 것이 바로 java.lang 패키지이다.
여기서 langLanguage(언어)의 줄임말이다. 쉽게 이야기해서 자바 언어를 이루는 가장 기본이 되는 클래스들을 보관하는 패키지를 뜻한다.

  • ✔️java.lang 패키지의 대표적인 클래스들
    • Object : 모든 자바 객체의 부모 클래스
    • String : 문자열
    • Integer, Long, Double : 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것
    • Class : 클래스 메타 정보
    • System : 시스템과 관련된 기본 기능 제공
  • ✔️import 생략 가능

🙄Object 클래스

  • ✔️Object 클래스
    • 자바에서 모든 클래스의 최상위 부모 클래스
    • 공통 기능 제공
    • 다형성의 기본 구현
  • ✔️공통 기능 제공
    • 객체의 정보를 제공하는 toString()
    • 객체의 같음을 비교하는 equals()
    • 객체의 클래스 정보를 제공하는 getClass()
    • 기타 여러가지 기능
  • ✔️다형성의 기본 구현
    • 부모는 자식을 담을 수 있다.
    • Object는 모든 클래스의 부모 클래스이다. 따라서 모든 객체를 참조할 수 있다.
    • 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();
		}
	}
}
  • ✔️Object 다형성의 한계
    • action() 메서드 안에서 obj.sound()를 호출하면 오류가 발생한다. 왜냐하면 매개변수 objObject 타입이기 때문이다. Object에는 sound() 메서드가 없다.
    • Object를 통해 전달 받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다.

🙄Object 배열

  • ✔️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);
	}
}

🙄toString()

  • ✔️toString()
    • 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
	}
}
  • ✔️toString() 오버라이딩
    • 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);
	}
}

🙄Object와 OCP

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를 사용하는 것을 BadObjectPrinterCar, 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 클래스에 의존한다고 표현한다.

❗추상적 : 여기서 말하는 추상적이라는 뜻은 단순히 추상 클래스나 인터페이스만 뜻하는 것은 아니다. AnimalDog, Cat의 관계를 떠올려보자. Animal같은 부모 타입으로 올라갈수록 개념은 더 추상적이고 Dog, Cat와 같이 하위 타입으로 내려갈수록 개념은 더 구체적이게 된다.

  • ✔️정적 의존관계 vs 동적 의존관계
    • 정적 의존관계는 컴파일 시간에 결정되며, 주로 클래스 간의 관계를 의미한다. 쉽게 이야기해서 프로그램을 실행하지 않고 클래스 내에서 사용하는 타입들만 보면 쉽게 의존관계를 파악할 수 있다.
    • 동적 의존관계는 프로그램을 실행하는 런타임에 확인할 수 있는 의존관계다. 앞서 ObjectPrinter.print(Object obj)에 인자로 어떤 객체가 전달 될지는 프로그램을 실행해봐야 알 수 있다. 어떤 경우에는 Car 인스턴스가 넘어오고, 어떤 경우에는 Dog 인스턴스가 넘어온다. 이렇게 런타임에 어떤 인스턴스를 사용하는지를 나타내는 것이 동적 의존관계이다.

🙄equals() - 1. 동일성과 동등성

Object는 동등성 비교를 위한 equals() 메서드를 제공한다.

자바는 두 객체가 같다라는 표현을 2가지로 분리해서 제공한다.

  • 동일성(Identity) : ==연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
  • 동등성(Equality) : 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);	// ==으로 비교
}

🙄equals() - 2. 구현

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() 메서드는 항상 동일한 값을 반환해야 한다.
    • null에 대한 비교 : 모든 객체는 null과 비교했을때 false를 반환해야 한다.

0개의 댓글