자바가 기본적으로 제공하는 라이브러리 중 가장 기본이 되는 것은 java.lang package
java.lang 패키지 대표적 클래스
Object = 모든 자바 객체의 부모 클래스
String = 문자열
Integer, Long, Double = 래퍼 타입, 기본형 데이터 터입을 객체로 만든것
Class = 클래스 메타 정보
System = 시스탬과 관련된 기본 기능들 제공
package chap22;
public class LangMain {
public static void main(String[] args) {
System.out.println("LangMain.main");
}
}
자바에서 모든 클래스의 최상위 부모 클래스는 항상 Object 클래스

package chap22.object;
//부모가 없으면 묵시적으로 Object 클래스를 상속
public class Parent {
//public class Parent extends Object
public void parentMethod() {
System.out.println("Parent.parentMethod");
}
}
클래스에 상속 받을 부모 클래스가 없다면 묵시적으로 Object 클래스를 상속
-> extends Object 코드를 넣어준다
package chap22.object;
//명시적으로 상속 받음
public class Child extends Parent{
public void childMethod(){
System.out.println("Child.childMethod");
}
}
클래스에상속 받을 부모 클래스를 명시적 지정 -> Object 상속 X
-> 쉽게 말해 이미 명시적으로 상속했기 때문에 extends Object 코드를 넣지 않는다.
package chap22.object;
public class ObjectMain {
public static void main(String[] args) {
Child child = new Child();
child.childMethod();
child.parentMethod();
//toString()은 object 클래스의 메서드
//toString은 객체의 정보를 제공해준다.
String string = child.toString();
System.out.println("string = " + string);
}
}
toString은 객체의 정보를 제공해준다.
실행 결과

child.toString() 호출
본인 타입인 child에서 toString을 찾는다. 없으므로, 부모 타입으로 올라가서 찾는다.
Parent에서 찾는다.
부모 타입인 Object에서 찾는다. Object에 toString이 있으므로 메서드를 호출
모든 클래스가 Object 클래스를 상속 받는 이유는 다음과 같다.
공통 기능 제공
객체의 정보를 제공, 이 객체가 다른 객체와 같은지 비교, 객체가 어떤 클래스로 형성, -> 모든 객체에게 필요한 기본 기능 (이런 기능이 없다면 매번 새로운 메서드 정의해서 만들어야 함)
Object는 모든 객체에 필요한 공통 기능을 제공, 모든 객체는 공통 기능을 편리하게 제공(상속)
Object 모든 클래스의 부모 클래스, Object는 모든 객체를 참조

public class Car {
public void move(){
System.out.println("움직인다.");
}
}
public class Dog {
public void sound(){
System.out.println("멍멍");
}
}
public class ObjectPolyEx1 {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
dog.sound();
action(dog);
action(car);
}
private static void action(Object obj){
//obj.sound(); -> obj 쪽에너는 sound가 없다.
//겍체에 맞는 다운 캐스팅 필요
if (obj instanceof Dog dog){
dog.sound();
} else if (obj instanceof Car car){
car.move();
}
}
}
Object는 모든 타입의 부모, 부모는 자식을 담을 수 있으므로 앞의 코드를 다음과 같이 변경가능
Object dog = new Object();
Object 다형성 장점
action(Object obj)
Object 타입의 매개변수를 사용, Object는 모든 객체의 부모
action(dog);
void action(Object obj)
action(car);
void action(Object obj)
action() 메서드 안에서 obj,sound를 호출하면 error
매개변수인 obj는 Object 타입 -> Object에는 sound 메서드 X

Dog 인스턴스의 sound를 호출하려면 다운케스팅 필요
if (obj instanceof Dog dog) {
dog.sound();
}

Object를 활용한 다형성의 한계
Object라고 해서 모든 객체를 알고 있는 것은 X
다형성을 제대로 활용하려면, 다형적 참조 + 메서드 오버라이딩
Object에 정의되어 있지 않은 메서드의 경우 활용 X -> 다운캐스팅
public class ObjectPolyEx2 {
public static void main(String[] args) {
Dog dog = new Dog();
Car car = new Car();
Object object = new Object();
Object[] obj = {dog, car, object};
size(obj);
}
private static void size(Object[] obj){
System.out.println("전달된 객체의 수 : " + obj.length);
}
}

size() 메서드
size(Object[] objects) 메서드는 배열에 담긴 객체의 수를 세는 역할
Object 없다면??
void action(Object obj) 과 같이 모든 객체를 받을 수 있는 메서드를 만들 수 없다.
Object[] objects 처럼 모든 객체를 저장할 수 있는 배열을 만들 수 없다
### toString
Object.toString() 메서드는 객체의 정보를 문자열 형태로 제공
public class ToStringMain1 {
public static void main(String[] args) {
Object o = new Object();
String string = o.toString();
//toString 반환값 출력
System.out.println("string = " + string);
//object 직접 출력
System.out.println("o = " + o);
}
}
object.toString
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Object가 제공하는 toString 메서드는 기본적으로 패키지를 포함한 객체의 이름과 객체의 참조값을 16진수로 제공
println & toString
toString() 의 결과를 출력한 코드와 object 를 println() 에 직접 출력한 코드의 결과가 완전히 같다.
System.out.println() 메서드는 사실 내부에서 toString() 을 호출한다
toString 오버라이딩
Object.toString 메서드가 클래스 정보와 참조값을 제공, toString을 재정의해서 보다 유용한 정보를 제공

public class Car {
private String carName;
public Car(String carName){
this.carName = carName;
}
}
public class Dog {
private String dogName;
private int age;
public Dog(String dogName, int age) {
this.dogName = dogName;
this.age = age;
}
//Object 상속 받기 때문에 toString 사용 가능
@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);
}
}
public class ToStringMain2 {
public static void main(String[] args) {
Car car = new Car("Model y");
Dog dog = new Dog("멍멍이 1", 2);
Dog dog2 = new Dog("멍멍이 2", 5);
System.out.println("1. 단순 toString 호출");
System.out.println(car.toString());
System.out.println(dog.toString());
System.out.println(dog2.toString());
System.out.println("2. println 내부에서 toString 호출");
System.out.println(car);
System.out.println(dog);
System.out.println(dog2);
System.out.println("3. Object 다형성 활용");
ObjectPrinter.print(car);
ObjectPrinter.print(dog);
ObjectPrinter.print(dog2);
//숫자로만 나옴
int i = System.identityHashCode(dog);
System.out.println("i = " + i);
//문자로 같이 나옴
String string = Integer.toHexString(System.identityHashCode(dog));
System.out.println("string = " + string);
}
}
ObjectPrinter.print(Object obj) 분석 - Car 인스턴스

* Object obj 인수로 car(Car) 전달
* 메서드 내부에서 obj.toString 호출
* obj는 Object 타입.
* 자식에 재정의된 메서드가 있는지 찾아본다, 재정의 된 메서드 X
* Object.toString 실행
ObjectPrinter.print(Object obj) 분석 - Dog 인스턴스

* Object obj 의 인수로 dog(Dog) 가 전달 된다.
* 메서드 내부에서 obj.toString() 을 호출한다.
* obj 는 Object 타입이다. 따라서 Object 에 있는 toString() 을 찾는다.
* 이때 자식에 재정의(오버라이딩)된 메서드가 있는지 찾아본다. Dog 에 재정의된 메서드가 있다.
* Dog.toString() 을 실행한다
참고)
toString은 기본적으로 객체의 참조값을 출력, toString이나 hashCode를 재정의하면 객체의 참조값을 출력 X
String refValue = Integer.toHexString(System.identityHashCode(dog1));
System.out.println("refValue = " + refValue);
### Object와 OCP
1. 추상적인 것에 의존
public class ObjectPrinter {
public static void print(Object obj) {
String string = "객체 정보 출력: " + obj.toString();
System.out.println(string);
}
}
추상적인 Object 클래스를 사용. ObjectPrinter 클래스가 Object 클래스를 사용하는 것을 ObjectPrinter 클래스가 Object에 클래스에 의존

다형적 참조
print(Object obj), Object 타입을 매개변수로 상용, Car, Dog 인스턴스를 포함한 세상의 모든 객체 인스턴스를 인수로 받고 있다.
메서드 오버라이딩
Object는 모든 클래스의 부모, Dog, Car와 같은 구체적 클래스는 Object가 갖고 있는 toString() 메서드를 오버라이딩 할 수 있다. 다. 따라서 print(Object obj) 메서
드는 Dog , Car 와 같은 구체적인 타입에 의존(사용)하지 않고, 추상적인 Object 타입에 의존하면서 런타임에 각 인스턴스의 toString() 을 호출할 수 있다.
OCP 원칙
Open : 새로운 클래스를 추가, toString()을 오버라이딩해서 기능 확장
Closed : 새로운 클래스를 추가해도 Object와 toString을 사용하는 클라이언트 코드인 ObjectPrinter는 변경X
다형적 참조, 메서드 오버라이딩, 클라이언트 코드가 구체적인 Car, Dog에 의존 X -> 추상적 Object 의존, OCP 원칙
### System.out.println()
System.out.println() 메서드도 Object 매개변수를 사용하고 내부에서 toString() 을 호출한다. 따라서 System.out.println() 를 사용하면 세상의 모든 객체의 정보( toString() )를 편리하게 출력할 수 있다.

자바 언어는 객체지향 언어 답게 언어 스스로도 객체지향 특징활용
참고 - 정적 의존관계 vs 동적 의존관계
정적 의존관계는 컴파일 시간에 결정되며, 주로 클래스 간의 관계를 의미한다. 앞서 보여준 클래스 의존 관계 그림이 바로 정적 의존관계이다. 쉽게 이야기해서 프로그램을 실행하지 않 고, 클래스 내에서 사용하는 타입들만 보면 쉽게 의존관계를 파악할 수 있다.
동적 의존관계는 프로그램을 실행하는 런타임에 확인할 수 있는 의존관계이다. 앞서
ObjectPrinter.print(Object obj) 에 인자로 어떤 객체가 전달 될 지는 프로그램을 실행 해봐야 알 수 있다. 어떤 경우에는 Car 인스턴스가 넘어오고, 어떤 경우에는 Dog 인스턴스가 넘어온다. 이렇게 런타임에 어떤 인스턴스를 사용하는지를 나타내는 것이 동적 의존관계이다.
참고로 단순히 의존관계 또는 어디에 의존한다고 하면 주로 정적 의존관계를 뜻한다
### equals() - 동일성 VS 동등성
동일성 : == 연산자를 사용해서 두 객체를 가리키고 있는지 확인, 물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인 하는 것
동등성 : equals 메서드를 사용해서 두 객체가 논리적으로 동등한지 확인, 자바 머신 기준이고 메모리의 참조가 기준임.
public class UserV1 {
private String id;
public UserV1(String id){
this.id = id;
}
}
public class EqualsMainV1 {
public static void main(String[] args) {
UserV1 user1 = new UserV1("id -100");
UserV1 user2 = new UserV1("id -100");
//둘의 참조값은 다르다.
System.out.println("identity = " + (user1 == user2));
System.out.println("identity = " + (user1.equals(user2)));
}
}

동등성 비교
Object.equals
public boolean equals(Object obj) {
return (this == obj);
}
기본적으로 ==으로 동일성 비교
package chap22.object.equals;
import java.util.Objects;
public class UserV2 {
private String id;
public UserV2(String id) {
this.id = id;
}
// @Override
// //나 자신과 넘어온 애랑 같은가??
// public boolean equals(Object o) {
// UserV2 userV2 = (UserV2) o;
// return id.equals(userV2.id);
// }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserV2 userV2 = (UserV2) o;
return Objects.equals(id, userV2.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Object의 equals 메서드를 재정의
equals는 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("identity = " + (user1.equals(user2)));
}
}
정확한 equals 구현
반사성 : 객체는 자기자신과 동등
대칭성 : 두 객체가 서로 동일하다고 판단하면, 양방향으로 동일
추이성 : 한 객체가 두 번째 객체와 동일하고, 두 번째 객체가 세번째 객체와 동일하다면, 첫번째 객체는 세번째 객체와도 동일
일관성 : 두 객체의 상태가 변경 X -> equals 메소드는 항상 동일한 값 반환
null 비교 : 모든 객체는 null과 비교했을때 false