_

Always be tactful

개인 학습/자바 스프링

[Java] 객체 지향 프로그램과 다형성

funczun 2025. 1. 27. 01:27

 자바는 객체 지향 언어다!


 객체 지향 프로그램의 대표적인 특징은 아래와 같다.

  1. 캡슐화
  2. 상속
  3. 다형성

▶ 프로그래밍에서 다형성은 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 말한다.


 다형성 이해를 위한 핵심 이론 두 가지은 아래와 같다.

  1. 다형적 참조
  2. 메서드 오버라이딩

▶ 다형적 참조는 말 그대로 다양한 형태로 참조될 수 있음을 말한다. (상속 관계 주의: 부모 클래스의 객체를 자식 클래스의 참조 변수에 할당하는 것은 타입 불일치로 간주된다.)


상속 예제

public class Parent {

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

    public void childMethod() {
        System.out.println("child method");
    }
}
public class Grandson extends Child {

    public void grandsonMethod() {
        System.out.println("grandson method");
    }
}

다형적 참조

Parent poly = new Parent(); // 가능
Parent poly = new Child(); // 가능
Parent poly = new Grandson(); // 가능
// Parent -> Child
Parent poly = new Child();

poly.parentMethod(); // 가능
poly.childMethod(); // 불가능

▶ Child 객체의 참조값을 담았지만, 결정적으로 Parent에는 Child에 대한 정보가 하나도 없다. 메서드 호출도 당연히 불가능하다.


캐스팅

// Parent -> Child (다형적 참조)
Parent poly = new Child();
// 다운 캐스팅 (부모 타입 -> 자식 타입)
Child child = (Child) poly;
child.childMethod(); // 가능
// 일시적 다운 캐스팅
((Child) poly).childMethod(); // 가능
// 업 캐스팅 (자식 타입 -> 부모 타입)
Child child = new Child();
Parent parent1 = (Parent) child; // 비권장
Parent parent2 = child; // 권장

권장이라고 표기하긴 했지만 사실상 생략하는 것이 맞다.

// 잘못된 다운 캐스팅
Parent parent = new Parent();
Child child = (Child) parent; // ClassCastException 발생
child.childMethod();

▶ new Parent()로 객체가 생성되어 메모리상 자식 타입이 존재하지 않는다. 따라서 사용 불가능한 타입 캐스팅에 대한 예외를 발생시킨다.

 

 업 캐스팅의 경우 이러한 예외를 걱정할 필요가 없다. 애초에 하위 타입을 만들면 상위 타입도 자동으로 생성되기 때문이다. IntelliJ에서 다운 캐스팅에 대해 경고를 해주는 반면, 업 캐스팅에 대해서는 경고하지 않는 이유가 위와 동일하다.


instanceof

객체가 특정 클래스의 인스턴스인지, 또는 특정 클래스의 하위 클래스에 속하는지 확인하는 연산자이다.

▶ 주로 객체의 타입을 확인할 때 사용되며, 형변환을 안전하게 수행하기 위해 사용한다.

// 사용법
<검사할 객체> instanceof <클래스 이름>
class Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();

        // animal 객체가 Dog의 인스턴스인지 확인
        if (animal instanceof Dog) {
            System.out.println("animal은 Dog의 인스턴스입니다.");
        } else {
            System.out.println("animal은 Dog의 인스턴스가 아닙니다.");
        }

        // animal 객체가 Cat의 인스턴스인지 확인
        if (animal instanceof Cat) {
            System.out.println("animal은 Cat의 인스턴스입니다.");
        } else {
            System.out.println("animal은 Cat의 인스턴스가 아닙니다.");
        }
    }
}

▶ 참고로 null 객체의 경우 항상 false를 반환한다.

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;  // 안전한 형변환
}

▶ 위와 같이 안전하게 형변환할 때 쓸 수 있다.


메서드 오버라이딩

// 부모 클래스
public class Parent {
    public String value = "parent";

    public void method() {
        System.out.println("Parent.method called");
    }
}
// 자식 클래스
public class Child extends Parent {
    public String value = "child";

    // 메서드 오버라이딩
    @Override
    public void method() {
        System.out.println("Child.method called");
    }
}
// 부모 변수가 자식 인스턴스 참조 (다형적 참조)
Parent poly = new Child();

System.out.println("value = " + poly.value); // parent 출력
poly.method(); // Child.method called 출력

▶ 오버라이딩 된 메서드는 항상 우선권을 가진다.

 

 위 예시에서는 변수 value의 경우, poly의 데이터 타입이 Parent이므로 Parent.value가 출력되었지만, 메서드의 경우 하위 클래스에서 오버라이딩 되었기 때문에 자식 클래스의 메서드가 호출되었다.