Programming Language/Java

[Java] 17. Object 클래스

lumana 2024. 10. 15. 16:40

 

Object 클래스

#Java


java.lang

lang은 Language의 줄임말로써, 자바 언어를 이루는데 기본이 되는 클래스를 모아둔 패키지이다.


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

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

import 생략 가능

모든 자바 application에 java.lang패키지가 자동으로 임포트되서 따로 구문을 적지 않아도 된다.


이번 챕터에서는 java.lang 패키지 중 Object 클래스에 대해 알아본다.


Object 클래스

클래스에 상속 받을 부모 클래스가 없으면, 묵시적으로 Object 클래스를 상속받는다.
반대로 명시적으로 지정하면 Object는 상속 받지 않는다.
(어차피 모든 객체의 최종 부모는 Object이다.)


Object 클래스를 왜 최상위 부모 클래스로 구현했을까?

두 가지 이유가 있다고 한다.

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

공통 기능 제공
참조변수가 같은 인스턴스를 가리키는지 확인하는 것, 객체의 클래스 정보를 확인하는 것 등등은 사실 모든 클래스의 객체에서 필요할 수 있는 기능들이다. 이걸 일일히 프로그래머가 구현하는 것 보단, 최상위 부모 클래스에서 공통 기능을 구현한 뒤 상속해주는게 당연히 더 편리하다. 또한 이렇게 함으로써, 모든 개발자가 같은 식별자(이름)를 가진 메서드를 호출할 수 있게 일관성을 갖게 된다는 장점도 있다.


Object 가 제공하는 기능은 다음과 같다.

  • 객체의 정보를 제공하는 toString()
  • 객체의 같음을 비교하는 equals()
  • 객체의 클래스 정보를 제공하는 getClass()
  • 기타 여러가지 기능

다형성의 기본 구현
부모는 모든 자식 객체를 참조할 수 있다. 이를 통해 다양한 타입의 객체를 통합적으로 처리할 수 있다. 타입이 다른 객체를 보관해야 한다면, Object에 보관해주면 된다.
먼저 다형성을 먼저 살펴보자


Object 다형성

package lang.object.poly;

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) {
        // 다운캐스팅

        if (obj instanceof Dog dog) {
            dog.sound();
        } else if (obj instanceof Car car) {
            car.sound();
        } else {
            System.out.println("지원하지 않는 타입입니다.");
        }
    }
}

타입이 다른 두 객체를 Object로 받은 뒤에, 각 객체의 클래스 정보에 맞는 메서드를 호출해주고 있다.


여기서 확인할 수 있는 장점은 Object를 이용하면 어떤 클래스의 객체든지 인자로 전달할 수 있다는 점이고, 한계라면 타입 체킹을 해주지 않으면 오류가 발생한다는 것이다. 참조 타입에 맞게 다운캐스팅을 해줘야 한다.
다형적 참조 + 오버라이딩을 Object를 통해서 사용하기에는 한계가 있다.


Object 배열

Object 배열을 통해 모든 타입의 객체를 받을 수 있다. 이 점을 활용하기 좋은 메서드가 size() 메서드이다.


size() 메서드
size(Object[] objects) 메서드는 배열에 담긴 객체의 수를 세주는 역할을 한다. 클래스가 추가되거나 변경되어도 이 메서드를 수정할 필요가 없다.


만약 Object가 없었다면 사용자가 클래스를 하나 만들어서(MyObject라고 해보자) 모든 클래스에서 직접 정의한 클래스를 상속받아야 한다. 하지만 서로 호환되지 않은 최상위 부모 클래스들이 매우 넘쳐날 것이다.



Object 메서드

공통 기능을 제공하는 Object 메서드를 살펴보자


toString()

객체의 정보를 문자열 형태로 제공한다.
정확히는 패키지를 포함한 객체 이름과 객체의 참조값(해시 코드)를 16진수로 제공한다.


println 메서드에 객체를 넣어주면, toString()의 결과를 출력해준다.
사실은 println이 오버로딩 되어있어서 Object 타입이 인수로 전달되면
내부에서 String.valueof(객체)가 실행되고, valueOf 메서드는 obj.toString()으로 문자열을 반환해준다.


toString() 오버라이딩
당연히 모든 클래스는 Object를 상속받기 때문에 toString() 메서드도 오버라이딩 할 수 있다. 상속받은 부모의 메서드가 객체의 상태를 명확하게 출력해주지 않는다고 판단되면 오버라이딩해주면 된다.


참고 - 객체의 참조값 직접 출력toString() 은 기본으로 객체의 참조값을 출력한다. 그런데 toString() 이나 hashCode() 를 재정의하면 객체의 참조값을 출력할 수 없다. 이때는 다음 코드를 사용하면 객체의 참조값을 출력할 수 있다.



String refValue = Integer.toHexString(System.identityHashCode(dog1));
System.out.println("refValue = " + refValue);


OCP와 Object 클래스

Object 클래스의 존재는, 추상적인 것에 의존하게 해준다는 의의가 있다.


OCP 원칙 “소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다”에 맞춰 생각해보자.

  • Open: 새로운 클래스가 추가되도, toString()을 오버라이딩해서 기능을 확장시킬 수 있다.
  • Closed : 새로운 클래스가 추가되어도, 추상적인 것(Object, toString())에 의존하는 클라이언트 코드를 변경하지 않아도 된다.

이 대표적인 예시가 System.out.println(obj) 메서드이다.

equals() 메서드 : 동일성과 동등성

Object동등성 비교를 위한 equals() 메서드를 제공한다.
(정확히는 동등성 비교를 위해서 오버라이딩 해야 한다. 아래서 자세히 알아보자)


  • 동일성(Identity): == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
    • 동일은 완전히 같음을 의미한다.
    • 물리적으로 같은 메모리에 있는 인스턴스인지 참조값을 확인하는 것
  • 동등성(Equality): equals() 메서드를 사용하여 두 객체가 논리적으로 동등한지 확인
    • 동등은 같은 가치나 수준을 의미하지만 그 형태나 외관이 완전히 같지 않을 수 있다.

동등성 예시

User a = new User("id-100") //참조 x001 
User b = new User("id-100") //참조 x002 

만약 오버라이딩 없이 equals() 메서드를 사용하면

UserV1 user1 = new UserV1("id-100");
UserV1 user2 = new UserV1("id-100");

System.out.println("identity = " + (user1 == user2));
System.out.println("equality = " + user1.equals(user2));

equals()메서드는 기본적으로 ==와 동일하게 동일성 비교를 제공한다.
하지만 오버라이딩을 통해서 동등성 비교로 바꿔줄 수 있다.


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

정확하게 동작하는 동등성 비교 equals() 메서드를 만들기 위해서는 다음 규칙을 지켜야 한다


equals() 메서드를 구현할 때 지켜야 하는 규칙

  • 반사성(Reflexivity): 객체는 자기 자신과 동등해야 한다. ( x.equals(x) 는 항상 true ).
  • 대칭성(Symmetry): 두 객체가 서로에 대해 동일하다고 판단하면, 이는 양방향으로 동일해야 한다. (x.equals(y)true 이면 y.equals(x)true ).
  • 추이성(Transitivity): 만약 한 객체가 두 번째 객체와 동일하고, 두 번째 객체가 세 번째 객체와 동일하다면, 첫 번째 객체는 세 번째 객체와도 동일해야 한다.
  • 일관성(Consistency): 두 객체의 상태가 변경되지 않는 한, equals() 메소드는 항상 동일한 값을 반환해야 한다.
  • null에 대한 비교: 모든 객체는 null 과 비교했을 때 false 를 반환해야 한다.

실무에서는 대부분 IDE가 만들어주는 equals()를 사용한다.


Ref) 김영한의 실전 자바 - 중급 1편