Object 클래스

고동현·2024년 7월 1일
0

JAVA

목록 보기
11/22

java.lang

자바가 기본으로 제공하는 라이브러리중에 가장 기본이 java.lang이다.

java.lang패키지의 대표적인 클래스

  • Object: 모든 자바 객체의 부모 클래스
  • String
  • Integer,Long,Double: 래퍼타입,기본형 데이터 타입 -> 객체로 만든것
  • Class: 클래스 메타정보
  • System: 시스템과 관련된 기본기능 제공

lang은 import를 하지 않아도 된다.
System.out.println을 해도 원래 System은 클래스인데, import를 하지 않아도 기본적으로 모든 자바 애플리케이션에 java.lang패키지가 자동으로 임포트된다.

Object

public class Parent {
    public void parentMethod(){
        System.out.println("Parent.parentMethod");
    }
}
public class Child extends Parent{
    public void childMethod(){
        System.out.println("Child.childMethod");
    }
}

Child는 명시적으로 Parent를 상속받았다.
아무것도 상속받지 않은 상위클래스 Parent는 묵시적으로 Object를 상속받는다.

그래서 모든 클래스의 최상단에는 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,Parent에 toString이라는 메서드가 없는데도 호출이 되는것을 볼 수 있다. 그이유는 parent의 부모가 Object이기 때문이다.


child.toString호출시
본인인 타입 Child에서 toString()을 찾고 없으므로 부모 타입으로 올라가서 찾게 된다.

Object클래스가 최상위 부모 클래스로 만들어 놓은 이유

  • 공통 기능 제공
  • 다형성의 기본 구현

공통기능 제공
toString, equals, getClass와 같은 기능을 하는 메서드를 모두 개발자가 개발하라고 하면, 일관성도 떨어지고 효율도 떨어지므로, 미리 만들어서 제공

다형성의 기본 구현
Object는 모든 객체를 다 담을 수 있으므로, 타입이 다른 객체를 어딘가에 보관해야한다면, Object에 보관하면 된다.

Object 다형성

Object는 모든 클래스의 부모 클래스이다. 따라서 Object는 모든 클래스를 참조 할 수 있다.

Object obj = new 어떤 클래스();

public class Dog {
    public void sound(){
        System.out.println("Dog.sound");
    }
}
public class Car {
    public void move(){
        System.out.println("Car.action");
    }
}
public class polyMain1 {
    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.sound();
        } else if (obj instanceof  Car car) {
            car.move();
        }
    }
}

obj.sound()
obj.move()
가 불가능하다.

왜냐하면 Object에는 해당 메서드들이 없기 때문이다.
메서드가 없으면 위로 올라갈 수는 있어도 아래로 내려갈 수는 없다.

그래서 다운캐스팅을 사용해야한다.

Object를 활용한 다형성의 한계

  • Object는 모든 객체의 부모이므로, 모든 객체를 참조할수있다.
  • Object를 통해 전달받은 객체를 호출하려면, 각 객체에 맞게 다운캐스팅이 필요하다.

다형성을 활용하기 위해서는 다형적 참조 + 메서드 오버라이딩을 함께 사용해야한다.
Object는 다형적 참조는 가능하지만, 메서드 오버라이딩이 불가능하다.(Object에는 다른 객체의 메서드들이 정의 안되어있으니까)

고로, Object를 활용한 다형성에는 한계가 있다.

그렇다면 Object는 언제 활용하면 좋은것일까?

Object 배열

public class polyMain2 {
    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);
    }
}


그림을 보면 Dog 인스턴스를 생성할때 Object 인스턴스도 같이 생성됨을 볼 수 있다.
Object와 같은 개념이 없다면
size와 같이 모든 객체를 받을 수 있는 메서드를 만들 수 없다.
objects처럼 모든 객체를 저장할 수 있는 배열을 만들 수 없다.

public class ToStringMain1 {
    public static void main(String[] args) {
        Object object = new Object();
        String s = object.toString();

        System.out.println(s);

        System.out.println(object);
    }
}
  • println(문자) - 문자를 출력한다.

  • println(인스턴스) - 인스턴스.toString()을 호출한다.

    a.lang.Object@10f87f48
    java.lang.Object@10f87f48

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;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "dogName='" + dogName + '\'' +
                ", age=" + age +
                '}';
    }
}

Dog에는 toString메서드를 오버라이드 해두었다.

public class ToStringMain2 {
    public static void main(String[] args) {
        Car car = new Car("ModelY");
        Dog dog1 = new Dog("dog1",2);
        Dog dog2 = new Dog("dog2", 4);

        System.out.println(car);
        System.out.println(dog1);
        System.out.println(dog2);

}

인스턴스를 println에 넘겨주면 인스턴스.toString을 호출하게 된다.
Car에는 toString오버라이드 된게 없으므로, Object의 toString이 호출
Dog는 오버라이드된 toString이 호출된다.

lang.object.toString.Car@2f4d3709
Dog{dogName='dog1', age=2}
Dog{dogName='dog2', age=4}

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("ModelY");
        Dog dog1 = new Dog("dog1",2);
        Dog dog2 = new Dog("dog2", 4);

        System.out.println(car);
        System.out.println(dog1);
        System.out.println(dog2);


        print(car);
        print(dog1);
        print(dog2);
    }
}

객체 정보 출력:lang.object.toString.Car@2f4d3709
객체 정보 출력:Dog{dogName='dog1', age=2}
객체 정보 출력:Dog{dogName='dog2', age=4}

Dog의 경우 toString호출시 오버라이딩 적용된 Dog의 toString이 호출된다.

Object를 이용한 다형성이 ObjectPrinter에서 나온다.

ObjectPrinter의 print메서드는 Car와 Dog와 같은 구체적인 class에 의존하지 않고, Object에 의존을 한다.
고로 Car,Dog뿐만 아니라 어떤 class가 오더라도 print메서드를 고칠 필요가 없다.

이는, println메서드에도 적용된다. println메서드 내부에는

Object로 파라미터를 받고 있다.
고로 내가 어떤 인스턴스를 만들던지 관계없이 println의 내부는 변경되지 않는다.
이는 모든 클래스의 최상단 부모인 Object로 설정하였기 때문이다.

equals()

  • 동일성: == 연산자를 통해서 두 객체의 참조가 동일한 객체를 가리키는지 확인
  • 동등성: equals()를 통해 두 객체가 논리적으로 동등한지 확인

User a = new User("id-100")
User b = new User("id-100")


둘은 다른 참조를 가리키고 있다. 당연히 동일성은 x이다.
그러나 id-100은 동일하니까 동등성은 O이다.

System.out.println(a.equals(b)); 를 실행하면 false가 나온다.
어? 동등성은 성립하니까 true여야하는거 아닌가? 싶지만

 public boolean equals(Object obj) {
 return (this == obj);
 }

equls메서드는 결국 ==으로 동일성으로 객체의 참조가 동일한 객체인지 확인하고있다.
어? 그러면 동등성은 판단 못하는거 아니냐? 맞다.

그러나 생각해보면, java입장에서는 인스턴스의 필드에 어떤게 올지 모르는 상태이니까 equals를 일단 ==으로 판단하는 수밖에 없다.

그래서 우리는 equals를 오버라이딩해서 사용해야만 한다.

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);
    }
}

equals메서드를 오버라이드 해주었다.
Object로 받아서 해당 타입으로 캐스팅해주고 id가 동일한지 비교한다.

public class EqualMainV2 {
    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));
    }
}

동일성을 비교하면 == false가 나온다. 참조값이 다르기 때문
equals를 호출하면 Obj의 equals가 아닌 오버라이드 된 UserV2의 equals가 실행된다.

참고: 해당 equals는 이해를 돕기위한 간단한 것이고, 실제 정확하게 동작하려면 alt+Insert로 IDE가 만들어주는 equals코드르 사용해야한다.

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글