성장일기

내가 보려고 정리하는 공부기록

언어/자바

[JAVA] Object 정리 (다형성, OCP..)

와나나나 2025. 9. 26. 22:44
728x90

요즘 자바 공부에 대한 필요성을 느끼고 있어서 강의 들으면서 정리를 좀 해보려고 한다!

인프런 김영한 강사님의 자바 중급1편 강의를 듣고 학습한 내용을 정리한 게시물입니다.

https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1

 

김영한의 실전 자바 - 중급 1편| 김영한 - 인프런 강의

현재 평점 5.0점 수강생 10,538명인 강의를 만나보세요. 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다. 실무에 필요한 다양한 자바 중급 기능, Object, 불변 객체, String

www.inflearn.com

 


1. Object 클래스

자바를 다루다보면 최상위객체가 Object라는 것을 자연스럽게 알게 된다. 그래서 상속받는 부모가 따로 없으면, 묵시적으로 Object 클래스를 상속받게 된다.

 

package lang.object;

public class Parent {

	public void parentMethod() {
    
    	System.out.println("Parent.parentMethod");
    }

}
package lang.object;

public class Parent extends Object {

	public void parentMethod() {
    
    	System.out.println("Parent.parentMethod");
    }

}

 

위 두개의 코드가 사실상 같은 코드라는 뜻이다!

 

묵시적? 명시적?
묵시적 : 개발자가 코드에 직접 기술하지 않아도 컴파일러에 의해 자동으로 수행되는 것
명시적 : 개발자가 코드에 직접 기술해서 작동하는 것

 

 

그렇다면 왜 자바에서 Object 클래스가 최상위 부모 클래스인걸까?

모든 클래스가 Object 클래스를 상속받음으로서 공통된 기능을 제공하고, 객체지향언어의 특징인 다형성의 기본 구현이 가능해진다.

 

공통된 기능

일부 객체가 아닌 모든 객체에 필요한 주요 기능들이 존재한다. 예를 들면, 객체의 정보를 확인하고, 해당 객체가 다른 객체와 같은지 비교하는 것, 이 객체가 어떤 클래스로 만든건지 확인하는 기능들이 있다.

만약 공통 부모클래스가 없다면, 모든 클래스에서 항상 새롭게 메서드를 정의해 만들어야 한다. 이렇게 되면 상당히 번거롭고, 일관성도 사라져 불편함을 겪게 될 것이다!

 

그래서 이러한 부분을 보완하기 위해 Object라는 공통 클래스를 만들고, 여기에 모든 객체에 필요한 공통기능을 구현해 제공한다. 그래서 우리는 Object를 상속받아 구현된 기능을 사용할 수 있게 된다.

 

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

  • toString() : 객체 정보 제공
  • equals() : 객체의 같음 비교
  • getClass() :객체의 클래스 정보 제공
  • 이 외에도 여러 메서드들이 제공된다!

이렇게 제공된 기능을 이용해 구현함으로서 단순화되고, 일관성있는 개발이 가능해진다.

 

 

다형성의 기본 구현

부모는 자식을 담을 수 있기 때문에, 모든 클래스의 부모클래스인 Object는 모든 객체를 참조할 수 있다.

모든 자바 객체는 Object타입으로 처리될 수 있고, 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 해준다!

 

객체 Car와 Dog를 생성했다고 한다면, 모든 객체의 최상위 부모는 Object이기 때문에 Object에 해당 객체를 넣을 수 있다.

private static void action(Object obj) {

	obj.sound();
	..
}

 

위 함수의 인자로 Dog나 Car를 넣을 수 있다는 의미이다. 만약 Dog를 넣었다면, dog의 내부 함수를 사용하고 싶을 수 있다. 함수를 sound() 라고 가정할 때, 위 코드처럼 obj.sound() 라고 쓰면 컴파일 오류가 발생한다. 

 

sound()라는 함수가 Object에 있는 게 아니라 Dog에 있기 때문이다. 함수를 사용할 때에는 해당 클래스에서 먼저 찾고, 없다면 해당 클래스의 부모클래스에서 찾게 된다. 그러나, Object는 이미 최상위 클래스이기 때문에 찾을 수 없어 오류가 발생하는 것이다.

 

이때 Object를 Dog로 바꿀 수 있는데, 이렇게 부모객체를 자신을 상속받는 자식객체로 바꾸는 것을 객체 다운캐스팅이라고 한다. 이때는 아래처럼 instanceof를 사용해주면 된다.

 

private static void action(Object obj) {

	if (obj instanceof Dog dog) {
    	dog.sound();
    }
	
	..
}

 

 

이렇게 object 클래스를 활용하면 모든 객체를 대상으로 다형적 참조를 할 수 있다. 쉽게 얘기하면 Object는 모든 객체의 부모이기 때문에, 모든 객체를 담을 수 있는 것이다.

단 Object를 통해 전달받은 객체를 호출하려면, Object가 세상의 모든 메서드를 알고있는 것은 아니기 때문에 맞는 객체로 다운캐스팅을 해주어야 한다.

 

다형성을 제대로 이용하기 위해서는 메서드 오버라이딩 + 다형성 참조를 함께 사용해야 한다.

하지만 이렇게 Object만을 이용하는 것은 오버라이딩이 안 되기 때문에 한계가 존재한다. object를 언제 사용하면 좋을지 알아보자.

 


2. toString(), equals() 메서드

toString()equals() 메서드는 Object에서 제공하는 메서드이다. 하나씩 알아보자.

 

 

toString()

Object.toString()은 객체의 정보를 문자열 형태로 제공하는 메서드이다. 보통 디버깅이나 로깅에 많이 사용한다.

이 메서드를 실행하면 객체의 참조값이 16진수로 제공된다. 아래는 Object.toString() 이다.

public String toString() {

	return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

 

실제 Object.toString()을 출력하면 아래처럼 결과가 출력된다.

// 코드
Object object = new Object();
System.out.println(object.toString());
System.out.println(object);

// 실제 결과
java.lang.Object@a099ee3
java.lang.Object@a099ee3

 

 

System.out.println() 은 내부에서 toString()을 호출하기 때문에, 그냥 객체만 출력해도 toString()이 적용되어 출력된다!

즉 System.out.println() 로 출력하면 그냥 객체를 출력하나 객체.toString()을 출력하나 결과는 같다는 것이다.

 

그런데 사실 저 결과로는 객체를 파악하기 힘들다. 우리가 원하는 것은 참조값보다는 더 유용한 정보들이기 때문이다. 이럴 때 우리는 toString()을 오버라이딩해서 원하는 결과를 출력하도록 한다.

 

@Override를 이용해 재정의를 하게 되면, 재정의된 메서드가 실행되고, 재정의를 하지 않으면 Object의 toString()을 사용한다.

 

public class Dog {

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

 

이런식으로 코드를 작성해준다.

 

 

 

Dog dog = new Dog();

System.out.println(dog.toString());
System.out.println(dog);

 

 

위 코드에서 toString()을 실행하는 순서는 다음과 같다.

  1. Object obj의 인수로 dog가 전달된다.
  2. 메서드 내부에서 obj.toString()을 호출한다.
  3. obj는 Object타입이므로 Object에 있는 toString()을 찾는다.
  4. 이때 자식에 오버라이딩된 메서드가 있는지 찾는다.
  5. 있다면 자식의 메서드를 실행시킨다.

 

equals()

Object는 동등성 비교를 위해 equals()를 제공한다. 자바는 두 객체가 같다는 표현을 두 가지로 분리해 제공한다.

  • 동일성 (Identity) : == 연산자를 사용해 두 객체의 차모가 동일한 객체를 가리키는지 확인
  • 동등성 (Equality) : equals() 를 사용해 두 객체가 논리적으로 동등한지 확인

 

동일은 물리적으로 완전히 같은 메모리에 존재하는, 완전한 겉음을 의미하고, 동등은 같은 가치를 갖는 논리적 동등을 의미한다.

즉 동일성은 메모리의 참조가 같은지가 기준이고 물리적이지만 동등성은 보통 사람이 생각하는 논리적인 기준에 맞춰 비교한다.

 

public class User {

	private String id;
    
    public User(String id) {
        this.id = id;
    }
}
public class Main {
	public static void main(String[] args) {
    	User user1 = new User("id100");
        User user2 = new User("id100");
    }
}

 

위 경우에서 동일성은 false, 동등성은 true인 셈이다. 그러나 Object에서 제공하는 equals() 는  ==으로 동일성을 비교하기 때문에, 오버라이딩을 통해 재정의 해주어야 한다.

 

오버라이딩 할 때 정확한 코드는 굳이 외우지 않아도 된다. (자동완성을 이용하라고 강사님께서 그러심)

 


3. Object와 OCP

만약 Object가 없고, Obejct가 제공하는 메서드들도 없다면 어떻게 될까? 객체에 대한 정보를 알기 위해 매번 새로운 메서드를 만들어야 한다. 공통 부모가 없기 때문에, 새로운 객체가 생길 때마다 같은 메서드를 만들어주어야 한다.

즉, 구체적인 것에 의존하게 된다.

 

매 클래스마다 같은 메서드를 생성하지 않기 위해서는 추상적인 클래스를 사용해야 한다. 객체를 보면 부모클래스로 갈수록 추상적인 클래스가 된다. 예를 들면 Cat 클래스 - Animal 클래스 - Object 클래스 이렇게 상속되는 것처럼 말이다.

이렇게 구체적인 타입에 의존하지 않고 추상적인 Object 타입에 의존하면서 런타임에 각 인스턴스의 Override된 메서드를 호출해 이용해야 다형성을 완전히 활용할 수 있게 된다.

 

 

OCP원칙 (Open-Closed Principle)

OCP원칙이란 확장에는 열려있고, 수정에는 닫혀있어야 한다는 원칙이다.

  • Open : 새로운 클래스를 추가하고, 오버라이딩을 이용해 기능을 확장할 수 있다
  • Closed : 새로운 기능을 추가해도 클라이언트 코드는 변경하지 않아도 된다

이렇게 다형적 참조, 메서드 오버라이딩을 이용하면 OCP원칙을 지킬 수 있게 된다.

 

 


정리를 하면서 처음 자바 공부를 할 때 온전히 이해하지 못한 것들이 이제와서는 이해가 되어 신기했다! 역시 기초를 한번 가져가는 시간이 필요한 거 같다. 강의 다 들으면서 정리해야겠삼