Java | 다형성

Lumpen·2025년 4월 8일
0

Java

목록 보기
13/38

다형성

객체지향 프로그래밍의 대표적인 특징으로는 캡슐화, 상속, 다형성이 있다
그 중에서도 다형성은 객체지향 프로그래밍의 꽃이라고 불린다

프로그래밍에서의 다형성은 한 객체가 여러가지 객체(타입)으로 취급될 수 있음을 뜻한다
하나의 객체는 하나의 타입을 갖지만 다른 타입으로도 사용될 수 있다

  • 다형적 참조
  • 메서드 오버라이딩

다형적 참조

부모 타입의 변수가 부모 인스턴스 참조

이 경우 부모 타입이 생성되기 때문에 자식은 함께 생성되지 않는다
메모리에 부모만 올라간다

자식 타입의 변수가 자식 인스턴스 참조

자이 경우 자식 타입을 생성했기 때문에 자식과 부모 모두 생성된다
메모리 상에 자식과 부모 모두 올라간다

다형적 참조 - 부모 타입의 변수가 자식 인스턴스 참조

  • 자식 인스턴스 생성 후 부모 타입의 변수에는 자식 인스턴스를 담을 수 있다
  • 부모와 자식은 메모리 상에 함께 생성되어 있다
  • 자식 타입은 부모 타입을 담을 수 없다
  • 부모 타입 변수는 자신을 포함한 하위의 모든 자식, 손자 타입을 참조할 수 있다 이를 다형적 참조라고 한다

다형적 참조와 인스턴스 실행

부모 타입의 변수가 참조하고 있는 자식 타입의 객체에서
메서드 실행 시 부모 타입의 메서드에서 필요한 기능을 찾는다 (호출자의 타입을 통해 대상 타입을 찾는다)

다형적 참조의 한계

부모 타입의 변수는 자식 타입의 기능을 참조할 수 없다
이런 경우 꼭 자식 타입의 기능이 필요하다면 타입 캐스팅이 필요하다

핵심은 부모 타입이 자식 타입을 담을 수 있다는 것이다

다형성 - 다운캐스팅

부모 타입의 변수에 담은 다형성 객체를 선언했지만
자식의 기능이 필요할 경우 명시적 형변환을 통해 다운캐스팅을 할 수 있다
기본적으로는 부모 타입의 객체를 자식 타입의 객체에 담는 것은 불가능하지만
일시적으로 자식 타입을 지정해줄 수 있다

Child child = (Child) poly;

이 경우 참조 값의 변수에 자식 타입을 지정하는 것으로
객체 자체의 타입이 변환되는 것은 아니다
변수 내의 값은 참조만 들어있기 때문이다

  • 업캐스팅: 부모 타입으로 변경
  • 다운캐스팅: 자식 타입으로 변경

일시적 다운 캐스팅

((Child) poly).childMethod; 
// 타입 캐스팅보다 우선순위가 메소드 실행에 있기 때문에 타입 캐스팅에 우선순위를 준다

업캐스팅

업캐스팅은 생략을 권장한다 (묵시적 형변환 활용)

Parent parent = (Parent) child

원래라면 이런 코드를 작성해야 하지만 암묵적으로 같은 동작이 일어난다

Parent parent = child

부모 타입으로의 형변환은 자연스러우니 따로 형변환을 해주지 않는 편이 좋다

다운캐스팅과 주의점

다운캐스팅을 잘못하면 심각한 런타임 오류가 발생할 수 있다 (ClassCastException)

  • 부모 인스턴스만 생성한 경우: 부모 인스턴스 생성 시 메모리에 자식 인스턴스는 함꼐 생성되지 않기 때문에 다운캐스팅을 할 수 없다

때문에 다운캐스팅을 자동으로 지원하지 않는다

업캐스팅은 안전하고 다운 캐스팅은 위험하다

업캐스팅의 경우 자식 인스턴스 생성 시 부모 타입 또한 함께 메모리에 생성되기 떄문에
항상 안전하다
다운 캐스팅의 경우 부모 인스턴스 생성 시 자식 타입은 함께 생성되지 않는다

컴파일 오류와 런타임 오류

  • 컴파일 오류: 오타 혹은 잘못된 클래스 이름 사용 등 자바 프로그램이 실행되기 전에 발생하는 오류로 IDE 에서 즉시 확인할 수 있기 떄문에 안전하고 좋은 오류
  • 런타임 오류: 프로그램이 실행되고 있는 시점에 발생하는 오류로 미리 발견하기 어렵고 프로그램이 동작 중에 중단되기 떄문에 매우 좋지 않은 오류

instanceof

다형성에서 참조형 변수는 다양한 자식을 대상으로 참조할 수 있다
어떤 인스턴스를 참조하고 있는지 알아보려면 instanceof 키워드를 사용한다

private static void call(Parent parent) {
	if (parent instanceof Child) {
    	System.out.println("Child 인스턴스");
        Child child = (Child) parent; 
        child.childMethod();
    }
}

위 처럼 다운캐스팅 수행 전 instanceof 사용하여 원하는 타입으로 변경 가능한지
확인 후에 다운 캐스팅을 수행하는 것이 안전하다

Pattern Matching for instanceof

private static void call(Parent parent) {
	if (parent instanceof Child child) { // 이 위치에서 위 코드의 child 변수 선언을 함께 해준다
    	System.out.println("Child 인스턴스"); 
        child.childMethod();
    }
}

다형성 - 메서드 오버라이딩

다형적 참조, 메서드 오버라이딩
오버라이딩 된 메서드가 항상 우선권을 가진다
메서드 오버라이딩은 다형성과 함께 사용되어 강력한 기능을 제공한다
오버라이딩은 메서드에만 적용된다

부모 타입 변수가 자식 인스턴스를 가질 때

오버라이딩 된 메서드를 가지고 있는 상속 관계의 두 인스턴스가 있을 때
다형적 참조를 통해 부모 타입 변수에서 자식 인스턴스를 참조하고 있는 다형성 객체는
필드는 부모의 것을 사용하지만
메서드는 오버라이딩 된 자식의 메서드가 우선 순위를 갖는다
상속 관계에서 생각하면 이러한 동작이 자연스럽다
동물 타입에 강아지를 넣고 오버라이딩 된 sound() 메서드를 호출했다고 생각해보면
당연히 강아지 울음 소리가 나와야 한다

부모 타입으로 부모를 선언하면 부모의 메서드만 존재하기 때문에 부모의 것을 사용
자식 타입으로 자식을 선언하면 오버라이딩 된 자신의 메서드 사용

다형성 활용

public static void main(String[] args) {
	Animal[] animalArr = {new Dog(), new Cat(), new Cow()};
    for (Animal animal : animalArr) {
    	soundAnimal(animal);
    }
    
    private static void soundAnimal(Animal animal) {
    	animal.sound();
    }
}

다형성과 메서드 오버라이딩을 이용하면
위 코드 처럼 형제 타입의 객체들을 하나의 배열을 담아 사용할 수 있다

좋은 코드

샷건효과: 하나의 코드 변경 시 함께 변경되어야 할 다른 코드의 범위에 대해 말하는 것
좋은 코드는 샷건효과의 범위가 적은 코드

profile
떠돌이 생활을 하는. 실업자, 부랑 생활을 하는

0개의 댓글