[JAVA] Object 클래스

나의 개발 일지·2024년 11월 30일

JAVA

목록 보기
5/11

우선, 글을 작성하기 전 이 글의 모든 내용은 김영한님의 JAVA 강의를 바탕으로 함을 알립니다.

💡Object 클래스

java.lang 패키지

java.lang 패키지는 java가 제공하는 가장 기본이 되는 라이브러리로, java를 이루는 가장 기본적인 클래스들의 모음이라고 보면 된다. java.lang 패키지의 대표적인 클래스는 다음과 같다.

  • Object : 모든 java 클래스의 부모 클래스
  • String : 문자열 클래스
  • Integer, Long, Double : 래퍼 타입 클래스 (기본형 데이터를 객체화한 것)
  • Class : 클래스의 메타 정보에 관한 클래스
  • System : 시스템과 관련된 기본 기능 제공

Object 클래스

java에서 항상 최상위 클래스는 Object 클래스이다. 즉, extends로 직접 부모 클래스를 언급하지 않았다는 것은 Object 클래스를 상속 받고 있음을 나타낸다.

묵시적 상속 vs 명시적 상속

위에서 언급했듯이, 직접 부모 클래스를 언급하지 않았다는 것은 '묵시적 상속'을 의미한다.

  • 묵시적(Implicit) : 개발자가 코드에 직접 기술하지 않아도 시스템 또는 컴파일러에 의해 자동으로 수행됨을 의미
  • 명시적(Explicit) : 개발자가 코드에 직접 기술하여 작동하는 것을 의미

Object 클래스의 기능

공통 기능 제공

  • 객체의 정보 제공, 객체간의 비교(동일성, 동등성), 객체가 어떤 클래스로 만들어졌는지 확인하는 기능은 모든 객체에게서 공통적으로 필요한 기능
  • 위의 기능이 공통 기능이 아닌 사용자 정의 기능이었다면 일관성이 없고 관리도 어려움
  • Object가 제공하는 공통 기능
    • toString() : 객체의 정보 제공 메서드
    • equals() : 객체의 같음을 비교하는 메서드 (동등성 비교)
    • getClass() : 객체의 클래스 정보를 제공하는 메서드
    • 이외의 다른 메서드

다형성의 기본 구현

Object는 모든 클래스의 부모 클래스이다. 즉, Object는 다형적 참조에 의해 모든 객체를 참조할 수 있음을 의미한다. 타입이 다른 객체들을 어딘가에 보관해야한다? Object 타입에 보관하면 된다.

Object 다형성

다음 예시를 보자

class Car {
	public void move() {
    	System.out.println("자동차 이동");
    }
}

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 instance of Car car) {
        	car.move();
        }
     }
}

위의 코드를 통해 Object 클래스의 장단점에 대해 알아보면 다음과 같다.

Ojbect 다형성의 장점

private static void action(Object obj) { ''' }

action 메서드의 매개변수는 Object 타입이다. 즉, Object는 모든 객체의 부모이기에 어떤 객체든지 인자로 전달이 가능함을 나타낸다.

Object 다형성의 한계

action(dog) //main에서 dog 전달
private static void action(Object obj) {
	obj.sound(); //컴파일 오류, Object는 sound()가 없다.
}

action() 메서드 안에서 obj.sound()를 호출하면 당연히 Object 클래스에는 sound()메서드가 없기에 오버라이딩이 불가능하고, sound()를 사용하기 위해서는 다운 캐스팅을 진행해야 한다.

정리하면,

  • Object 클래스는 모든 클래스의 부모 클래스이기에 모든 객체를 상대로 다형적 참조가 가능하다
  • Object 타입을 통해 전달 받은 객체를 호출 하기위해서는 해당 객체로 다운 캐스팅이 필요하다.

다형적 참조는 OOP의 핵심중 하나로 제대로 활용하기 위해서는 다형적 참조메서드 오버라이딩을 함께 사용해야한다. 물론 Object가 제공하는 toString(), equals(), getClass()와 같은 메서드는 오버라이딩이 가능하나 특정 객체가 갖고 있는 고유한 기능은 Object 클래스에 정의되어 있지 않기에 다운 캐스팅이 수반되어야 하고 이는 Object 클래스의 다형성이 한계가 있음을 보여준다. 그렇다면 Object 클래스는 언제 최대로 활용할 수 있을까?

Obejct 배열

Object는 모든 객체를 담을 수 있다. 따라서 Object 타입의 배열은 모든 객체를 담을 수 있는 배열이 된다.

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 배열을 메모리 관점에서 보면 다음과 같다.

toString()

Object 클래스의 toString()메서드는 객체의 정보를 제공하는 역할을 한다. 기본적으로 패키지를 포함한 객체 이름과 객체 참조값을 제공한다. 실행 결과 예시는 다음과 같다.

java.lang.Object@a09ee92 // 패키지명+객체이름+객체 참조값

Object 클래스의 toString()은 위와 같은 결과를 제공하는데, 위와 같이 객체의 참조값이 아닌 사용자 정의에 의해 특정 형태로 객체의 정보를 제공하고 싶다면 모두의 부모인 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() { // toString() 재정의 
      return "Dog{" +
              "dogName='" + dogName + '\'' +
              ", age=" + age +
              '}';
 	}
}

기본적으로 toString()은 객체의 메모리 참조값을 제공하는데, 오버라이딩을 통해 재정의할 경우 다른 정보를 출력할 수 있다. 이런 경우 참조값을 따로 출력하고 싶으면 다음과 같이 진행하면 된다.

String refValue = Integer.toHexString(System.identifyHashCode(dog1))
System.out.println("refValue = " + refValue);  // refValue = 72ea2f77

Object와 OCP

public class ObjectPrinter {
	public static void print(Object obj) {
 		String string = "객체 정보 출력: " + obj.toString();
 		System.out.println(string);
 	}
}

OCP(Open-Closed Principle)

OCP는 객체지향 설계 원칙 중 하나로, "클래스는 확장에 열려 있고 변경에는 닫혀 있어야 한다"는 개념을 의미한다.

  • Open : 확장에 열려 있다
    • 새로운 기능이나 요구사항이 추가될 때, 기존 코드를 변경하지 않고도 확장이 가능해야 한다
  • Closed : 수정에 닫혀 있다
    • 기존 코드는 안정적이어야 하며, 변경하지 않아도 새로운 요구사항을 반영할 수 있도록 설계되어야 한다.

Object와 OCP

  • Open : 새로운 클래스를 추가하고, toString()을 오버라이딩하여 기능 확장이 가능하다
  • Closed : 새로운 클래스를 계속해서 추가해도 Object와 toString()을 사용하는 클라이언트 코드인 ObjectPrinter은 변경없이 사용 가능

equals() - 동일성과 동등성

java는 두 객체의 '같음'을 2가지로 표현한다.

  1. 동일성(Identity) : ==연산자를 통해 두 객체가 동일한 메모리를 가리키고 있는지 확인
  2. 동등성(Equality) : equals() 메서드를 사용하여 두 객체가 논리적으로 동등한지 확인
User a = new User("id-100") //참조 x001
User b = new User("id-100") //참조 x002

위의 예시의 경우 물리적으로 다른 객체에 존재하지만 논리적으로 동일한 회원 번호를 갖고 있기에 같은 회원으로 볼 수 있다. 따라서 동일성은 false이지만 동등성은 true인 것이다.

예제를 통해 동일성과 동등성에 대해 알아보자

public class UserV1 {

	private String id;
    //constructor
	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));  //false
 		System.out.println("equality = " + user1.equals(user2)); //false
 	}
}

id값이 id-100으로 동일한 user1과 user2의 identity(동일성)과 equality(동등성)을 확인한 것으로, 위에서 언급한 동일성과 동등성 개념에 의하면 true, false가 출력되어야 한다. 하지만 equality(동등성)는 id값이 동일함에도 불구하고 false가 나왔다. 어떻게 된것일까?

Object.equals()의 소스 코드를 살펴보자.

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

소스코드에 의하면, equalsthis==obj 즉, 동일성을 기반으로 작성되어 있음을 확인할 수 있다. 따라서, 서로 다른 객체인 user1과 user2의 참조값은 다르기에 equals()를 사용한 비교도 false가 출력될 수 밖에 없다.

그렇다면 동등성 비교는 어떻게 해야하는 걸까?

동등성(equality) 비교를 논리적으로 생각해보면 동등성 비교는 비교 순간마다 비교의 기준이 달라질 수 있음을 알아야한다. 즉, 동등함의 기준이 id일수도 있고 주민번호일수도 있으며 연락처일수도 있는 것이다. 따라서, 동등성 비교를 해야한다면 Object.equals()를 오버라이딩을 통한 재정의를 해야한다.

equals()의 경우 intellij에서 제공하는 Alt+Insert를 통해 제공하는 equals()를 보편적으로 사용한다.

public class UserV2 {
	private String id;
    
 	public UserV2(String id) {
 		this.id = id;
 	}
    
    //equals() 재정의 
 	@Override
 	public boolean equals(Object obj) {
 		UserV2 user = (UserV2) obj;
 		return id.equals(user.id);
 	}
}

0개의 댓글