[JAVA] 다형성_1
다형성이란?
- 다형성(Polymorphism)은 다양한 형태를 의미.
- 프로그래밍에서 다형성이란, 하나의 객체가 여러 타입으로 참조될 수 있는 능력을 말한다.
보통 하나의 객체는 하나의 타입으로만 사용된다.
하지만 다형성을 사용하면 하나의 객체가 여러 타입으로 사용될 수 있다.
다형성을 이해하려면 두 가지 개념을 알아야 한다:
- 다형적 참조 (Polymorphic Reference)
- 메서드 오버라이딩 (Method Overriding)
다형적 참조 (Polymorphic Reference)
다형성을 이해하려면 먼저 다형적 참조 개념을 알아야 한다. 다형적 참조는 부모 타입의 변수로 자식 인스턴스를 참조하는 것 을 의미한다.
1
2
3
4
5
6
7
8
// Parent.java
package poly.basic;
public class Parent {
    public void parentMethod() {
        System.out.println("Parent.parentMethod");
    }
}
1
2
3
4
5
6
7
8
// Child.java
package poly.basic;
public class Child extends Parent {
    public void childMethod() {
        System.out.println("Child.childMethod");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// PolyMain.java
package poly.basic;
public class PolyMain {
    public static void main(String[] args) {
        System.out.println("Parent -> Parent");
        Parent parent = new Parent();
        parent.parentMethod();
        System.out.println("Child -> Child");
        Child child = new Child();
        child.parentMethod();   // 상속받은 메서드 호출 가능
        child.childMethod();    // 자식 고유 메서드 호출 가능
        System.out.println("Parent -> Child");
        Parent poly = new Child();
        poly.parentMethod();    // 부모 타입에서 선언된 메서드만 호출 가능
        // poly.childMethod(); // 컴파일 오류 - Parent 타입에는 존재하지 않는 메서드
    }
}
// 실행결
Parent -> Parent
Parent.parentMethod
Child -> Child
Parent.parentMethod
Child.childMethod
Parent -> Child
Parent.parentMethod
부모 타입 변수 → 부모 타입 인스턴스
1
Parent parent = new Parent();
- Parent클래스만 메모리에 생성된다.
- parent.parentMethod()호출 시- Parent클래스의 메서드가 실행된다.
자식 타입 변수 → 자식 타입 인스턴스
1
Child child = new Child();
- Child와- Parent클래스 모두 메모리에 생성된다.
- 자식 변수는 부모 메서드와 자식 메서드 모두 호출할 수 있다.
부모 타입 변수 → 자식 타입 인스턴스 (다형적 참조)
1
Parent poly = new Child(); //업캐스팅
- Child와- Parent클래스 모두 메모리에 생성된다.
- 변수의 타입이 Parent이므로,Parent에 정의된 메서드만 호출할 수 있다.
- 자식 고유 메서드인 childMethod()는 호출할 수 없다. (컴파일 오류)
1
poly.childMethod(); // 컴파일 오류 발생
- poly는- Parent타입이므로- Parent에 정의되지 않은 메서드는 호출할 수 없다.
- 부모 타입은 자식의 기능을 알지 못하므로 접근할 수 없다.
- 이럴 경우 형변환(캐스팅)을 통해 접근해야 한다.
캐스팅
1
2
Child casted = (Child) poly; // 형변환 캐스팅
casted.childMethod(); // 가능
형변환을 사용하면 자식 타입에만 있는 기능도 사용할 수 있다.
이처럼 부모 타입의 참조 변수를 자식 타입으로 변환하는 것을 다운캐스팅(downcasting) 이라고 한다.
Child child = (Child) poly; 이 코드는 poly가 가리키는 인스턴스(실제 객체)가 Child 타입이라는 것을 가정하고, 그 참조값을 Child 타입 변수에 대입하는 것이다.
참고: poly의 참조값만 복사되는 것이고,
poly자체의 타입은 여전히Parent이다. 변하는 것은 아니다.
일시적 다운캐스팅
다운캐스팅 결과를 변수에 담지 않고, 필요한 순간에만 일시적으로 타입을 변환해서 메서드를 호출할 수도 있다.
1
((Child) poly).childMethod();
- poly는- Parent타입이지만, 괄호를 두 번 감싸- (Child)로 일시적으로 타입을 변환한 뒤, 곧바로- childMethod()를 호출한다.
- 이 방식은 변수를 따로 만들 필요가 없어서 간편하다.
- 물론 실제 인스턴스가 자식 타입이어야만 가능하다.
이 캐스팅은 단지 poly가 가리키는 참조값을 Child로 해석해서 호출하는 것 뿐이다. 실제로 poly의 타입 자체가 바뀌는 것은 아니다. 즉, poly는 여전히 Parent 타입 이다.
업캐스팅
다운캐스팅과 반대로, 자식 타입을 부모 타입으로 변환하는 것을 업캐스팅 이라고 한다.
1
2
3
4
5
6
7
Child child = new Child();
Parent parent1 = (Parent) child; // 명시적 업캐스팅 (사실 생략 가능)
Parent parent2 = child;          // 암묵적 업캐스팅 (추천 방식)
// 실행 결과
Parent.parentMethod
Parent.parentMethod
- Child인스턴스를- Parent타입으로 참조하는 것은 업캐스팅이다.
- 이 경우 형변환 코드를 생략해도 자동으로 변환된다.
- 자바에서는 업캐스팅을 매우 자주 사용하므로 생략하는 것이 일반적이다.
1
Parent parent = new Child(); // 업캐스팅 생략한 형태
업캐스팅은 자바 컴파일러가 자동으로 처리해준다. 반면, 다운캐스팅은 명시적인 형변환이 없으면 컴파일 오류가 발생 한다.
다운캐스팅과 주의점
다운캐스팅은 실제 인스턴스가 자식 타입일 때만 안전 하게 사용할 수 있다. 잘못된 다운캐스팅은 ClassCastException이라는 런타임 오류를 발생시킨다.
1
2
3
4
5
6
7
Parent parent1 = new Child();
Child child1 = (Child) parent1;
child1.childMethod(); // OK
Parent parent2 = new Parent();
Child child2 = (Child) parent2; // 런타임 오류 발생
child2.childMethod(); // 실행되지 않음
왜 오류가 생길까
1
2
Parent parent2 = new Parent(); // 실제 인스턴스는 Parent
Child child2 = (Child) parent2; // 존재하지 않는 기능에 접근하려고 함
- parent2는- Parent타입 인스턴스이기 때문에, 메모리에- Child관련 정보가 없음
- 그런데 Child로 변환하려 하니까 자바가 런타임 예외를 발생시킴
- 이 예외가 바로 ClassCastException
1
2
Child.childMethod
Exception in thread "main" java.lang.ClassCastException: class poly.basic.Parent cannot be cast to class poly.basic.Child
안전하게 캐스팅하려면?
자바에서는 다운캐스팅 전에 instanceof 키워드를 사용해서 실제 인스턴스 타입을 검사할 수 있다.
1
2
3
4
5
6
if (parent2 instanceof Child) {
    Child safeChild = (Child) parent2;
    safeChild.childMethod();
} else {
    System.out.println("Child 타입이 아닙니다.");
}
→ 이렇게 검사하면 런타임 오류 없이 안전하게 다운캐스팅할 수 있다.
업캐스팅은 안전하고, 다운캐스팅은 왜 위험할까?
업캐스팅은 항상 안전하다
1
A a = new C(); // 업캐스팅
- 클래스 A → B → C 순으로 상속 관계가 있다고 가정하자.
- new C()를 하면 메모리에는- C,- B,- A모두 함께 생성된다.
- 따라서 상위 타입인 A, B, C 어느 타입으로 참조해도 문제 없다.
- 이처럼 위로 올라가는 업캐스팅은 인스턴스 내부에 모든 타입 정보가 포함되어 있어 안전하다.
그래서 업캐스팅은 형변환 생략도 가능하고, 자바에서 매우 자주 쓰인다
다운캐스팅은 조심해야 한다
1
C c = (C) new B(); // 런타임 오류 (ClassCastException)
- new B()로 생성하면 인스턴스에는- B,- A만 존재한다.
- 그런데 C는 존재하지 않기 때문에C로 캐스팅하면 없는 기능에 접근하려는 시도다.
- 자바는 이 상황에서 ClassCastException이라는 런타임 오류를 발생시킨다.
하위 타입은 객체 생성 시 포함되지 않기 때문에, 존재하지 않는 부분을 참조하게 될 위험이 있다.
컴파일 오류 vs 런타임 오류
| 구분 | 설명 | 예시 | 확인 시점 | 
|---|---|---|---|
| 컴파일 오류 | 문법적 오류, 즉시 확인 가능 | 변수 오타, 클래스 없음 | 코드 작성 시 | 
| 런타임 오류 | 실행 중 발생하는 오류 | 잘못된 다운캐스팅 | 프로그램 실행 중 | 
- 컴파일 오류는 IDE가 바로 알려주기 때문에 안전하다.
- 런타임 오류는 실제 사용자 앞에서 발생할 수 있기 때문에 위험하다.
- 그래서 다운캐스팅은 항상 조심해서 사용해야 한다.
instanceof
다형성에서는 부모 타입 변수가 다양한 자식 인스턴스를 참조할 수 있기 때문에,
현재 참조하고 있는 실제 인스턴스의 타입을 확인할 필요가 있다.
이때 사용하는 것이 바로 instanceof 연산자이다.
1
2
Parent parent1 = new Parent();
Parent parent2 = new Child();
- parent1은- Parent인스턴스를 참조한다.
- parent2는- Child인스턴스를 참조한다.
이처럼 부모 타입 변수는 다양한 자식 인스턴스를 참조할 수 있으므로,
다운캐스팅 전에 instanceof로 검사하는 것이 안전한 코드 작성에 도움이 된다.
instanceof 결과 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void call(Parent parent) {
    parent.parentMethod();
    if (parent instanceof Child) {
        System.out.println("Child 인스턴스 맞음");
        Child child = (Child) parent; // 안전한 다운캐스팅
        child.childMethod();
    }
}
new Parent() instanceof Parent  // true
new Child() instanceof Parent   // true (부모 타입도 포함됨)
new Parent() instanceof Child   // false
new Child() instanceof Child    // true
- 왼쪽 인스턴스를 오른쪽 타입에 대입할 수 있는지 판단하는 구조
- 대입 가능하면 true, 불가능하면false를 반환한다
Java 16부터: 패턴 매칭 기반 instanceof
Java 16부터는 instanceof를 사용할 때 타입 검사 + 캐스팅 + 변수 선언을 한 번에 처리할 수 있다.
1
2
3
if (parent instanceof Child child) {
    child.childMethod();
}
- 위 코드는 parent가Child타입인 경우만 실행된다.
- 동시에 Child child = (Child) parent를 자동으로 수행한 효과를 가진다.
- 불필요한 캐스팅 없이 더 간결한 코드 작성이 가능하다.
정리
- instanceof는 다운캐스팅의 안전성을 확보하기 위한 필수 도구이다.
- 실제 인스턴스 타입을 확신할 수 없을 때는 instanceof로 검사한 뒤 캐스팅해야 한다.
- Java 16 이상에서는 패턴 매칭을 활용해 더 깔끔하게 작성할 수 있다.
다형성과 메서드 오버라이딩
다형성을 이루는 또 하나의 핵심 이론은 바로 메서드 오버라이딩(Overriding) 이다. 오버라이딩은 부모 클래스에 정의된 메서드를 자식 클래스에서 재정의하는 것이다.이때 중요한 점은, 오버라이딩 된 메서드는 항상 우선권을 가진다.
다형성과 함께 사용할 때, 메서드 오버라이딩의 진짜 힘이 발휘된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Parent.java
public class Parent {
    public String value = "parent";
    public void method() {
        System.out.println("Parent.method");
    }
}
// Child.java
public class Child extends Parent {
    public String value = "child";
    @Override
    public void method() {
        System.out.println("Child.method");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class OverridingMain {
    public static void main(String[] args) {
        Child child = new Child();
        System.out.println("Child -> Child");
        System.out.println("value = " + child.value);
        child.method();
        Parent parent = new Parent();
        System.out.println("Parent -> Parent");
        System.out.println("value = " + parent.value);
        parent.method();
        Parent poly = new Child();
        System.out.println("Parent -> Child");
        System.out.println("value = " + poly.value); // 변수는 오버라이딩되지 않음
        poly.method(); // 메서드는 오버라이딩됨
    }
}
//실행결과
Child -> Child
value = child
Child.method
Parent -> Parent
value = parent
Parent.method
Parent -> Child
value = parent
Child.method
- 변수는 타입에 따라 결정된다 → 정적 바인딩
- 메서드는 실제 인스턴스에 따라 결정된다 → 동적 바인딩
오버라이딩의 우선순위
오버라이딩된 메서드는 항상 하위 클래스 쪽 메서드가 우선순위를 가진다.
예를 들어,
- 부모 → 자식 → 손자 클래스가 있고,
- 셋 다 method()를 오버라이딩했다면,
- 손자의 메서드가 실행된다. 즉, 더 하위 클래스일수록 우선순위가 높다.
정리
- 다형적 참조: 부모 타입 변수로 자식 인스턴스를 참조할 수 있다.
- 메서드 오버라이딩: 자식이 부모의 메서드를 재정의하여 새로운 동작을 제공한다.
- 
    다형성과 오버라이딩을 함께 사용하면, 부모 타입 변수를 통해 자식의 오버라이딩된 메서드를 실행할 수 있다. 
이 조합이 바로 다형성의 진짜 위력이며, 유연하고 확장 가능한 객체지향 설계의 핵심이다.
 출처: 김영한의 실전 자바 - 기본편




