다형성 시작
객체지향 프로그램의 대표적 특징 -> 캡슐화, 상속, 디형성
다형성은 이름 그대로 -> 다양한 형태, 여러 형태
프로그래밍에서 다형성은 한 객체가 여러 타입으로 객체로 취급될 수 있는 능력. 보통 하나의 객체는 하나의 타입으로 고정. 다형성을 사용하면, 하나의 객체가 다른 타입으로 사용.
다형적 참조
public class Parent {
public void parentMethod(){
System.out.println("Parent.parentMethod");
}
}
public class Child extends Parent{
public void childMethod(){
System.out.println("Child.childMethod");
}
}
public class BasicMain {
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.childMethod();
child.parentMethod();
//부모 변수가 자식 인스턴스 참조 (다형적 참조)
System.out.println("Parent -> Child");
Parent basic = new Child();
basic.parentMethod();
}
}
부모 타입의 변수가 부모 인스턴스 참조

부모 타입의 변수가 부모 인스턴스를 참조
Parent 인스턴스를 생성. 이 경우 부모 타입인 Parent를 생성. 메모리상에 Parent만 생성
생성된 참조 값을 Paretn 타입의 변수인 Parent에 담아둔다.
parent,parentMethod를 호출, 인스터는스의 Parent 클래스에 있는 parentMethod가 호출
자식 타입의 변수가 자식 인스턴스 참조

자식 타입의 변수가 자식 인스턴스 참조
child 인스턴스의 경우 메모리상에 child Parent가 모두 생성
생성된 참조값을 child 타입의 변수인 child에 담아둔다.
다형적 참조
부모 타입의 변수가 자식 인스턴스 참조

부모 타입의 변수가 자식 인스턴스 참조
Parent poly = new Child()
Child 인스턴스를 생성. 이 경우 자식 타입인 Child만 생성, 메모리상에 Child와 Parent 모두 생성.
생성된 참조값 Parent 타입읜 변수인 poly에 담아둔다.
부모는 자식을 담을 수 있다.
부모 타입은 자식 타입을 담을 수 있다.
Parent poly는 부모 타입, new Child를 통해 생성된 결과는 Child 타입
자식 타입은 부모 타입을 담을 수 없다.
다형적 참조
부모 타입의 변수는 -> 자식, 그 하위 타입 까지 참조 가능
다형적 참조와 인스턴스 싱행
poly.parentMethod를 호출하면 우선 참조값을 사용해서 인스턴스를 찾는다.
다음으로 인스턴스 안에서 실행할 타입도 찾는다. poly는 Parent 타입이다. Parent 클래스부터 시작해서 필요한 기능을 찾는다.
다형적 참조의 한계

poly.childMethod를 실행하면 먼저 참조값을 통해 인스턴스를 찾는다. 그리고 다음으로 인스턴스 안에서 실행할 타입을 찾는다. 호출자인 poly는 Parent 타입이다. 따라서 Parent 클래스로 시작해서 필요한 기능을 찾는다. 상속관계는 부모 방향으로 올라갈 수 O, 자식 방향으로 내려갈 수 X
해당 메서드 X -> compileError
다형성과 캐스팅
public class CastingMain1 {
public static void main(String[] args) {
//부모 변수가 자식 인스턴스 참조 (다형적 참조)
Parent poly = new Child();
//단 자식의 기능은 호출 할 수 없다
//poly.childMethod();
//다운 캐스팅 (부모 --> 자식)
Child child = (Child) poly;
child.childMethod();
}
}

다운 캐스팅

부모는 자식을 담을 수 있지만 자식은 부모를 담을 수 X
반면에 다음과 같은 자식은 부모를 담을 수 X
Child child = poly //Parent poly 변수
-> CompileError
Child child = (Child) poly //Parent poly
(타입) 처럼 괄호와 그 사이에 타입을 지정, 특정 타입으로 변경 가능. -> 특정 타입으로 변경하는 것을 캐스팅
오른쪽에 있는 (Child) poly 코드를 먼저 보자. poly 는 Parent 타입이다. 이 타입을 (Child) 를 사용해서 일
시적으로 자식 타입인 Child 타입으로 변경한다. 그리고 나서 왼쪽에 있는 Child child 에 대입한다
Child child = (Child) poly //다운캐스팅을 통해 부모타입을 자식 타입으로 변환한 다음에 대입 시도
Child child = (Child) x001 //참조값을 읽은 다음 자식 타입으로 지정
Child child = x001 //최종 결과
캐스팅
upcasting -> 부모타입으로 변경
downCasting -> 자식타입으로 변경
//다운캐스팅(부모 타입 -> 자식 타입)
Child child = (Child) poly;
child.childMethod();
캐스팅의 종류
자식 타입의 기능을 사용하려면 다운캐스팅 결과를 변수에 담아두고 기능 실행
하지만 다운캐스팅 결과를 변수에 담아두는 과정은 번거로움.
일시적 다운 캐스팅
public class CastingMain2 {
public static void main(String[] args) {
//부모 변수가 자식 인스턴스 참조 (다형적 참조)
Parent poly = new Child();
//단 자식의 기능은 호출 할 수 없다
//poly.childMethod();
//다운 캐스팅 (부모 --> 자식)
Child child = (Child) poly;
child.childMethod();
//일시적 다운 케스팅 -> 해당 메서드를 호출하는 순간만 다운케스팅
((Child) poly).childMethod(); //연산자 우선순위
}
}

((Child) poly).childMethod()
poly는 Parent type -> 이 코드를 실행하면 Parent 타입을 임시로 child로 변경
정확히는 poly가 Child 타입으로 변경되는 것 X
((Child) poly).childMethod() //다운캐스팅을 통해 부모타입을 자식 타입으로 변환 후 기능 호출
((Child) x001).childMethod() //참조값을 읽은 다음 자식 타입으로 다운캐스팅
참고로 캐스팅을 한다고 해서 Parent poly 타입이 변하는 것이 아니다. 해당 참조값을 꺼내고 꺼낸 참조값이 child 타입이 되는 것
일시적 다운캐스팅 사용, 별도 변수 없이 인스턴스의 자식 타입의 기능을 사용
UpCasting
다운캐스팅과 반대로 현재 타입을 부모타입으로 변경하는것
public class CastingMain3 {
public static void main(String[] args) {
Child child = new Child();
Parent parent1 = new Parent();
Parent parent2 = child;
parent1.parentMethod();
parent2.parentMethod();
}
}
= UpCasting은 생략 O, downCasting은 생략 X
DownCasting은 왜 명시적으로 표현 해줘야 하는 걸까 ??
다운캐스팅은 잘못하면 심각한 런타임 오류가 발생 할 수 있다.
public class CastingMain4 {
public static void main(String[] args) {
Parent parent = new Child();
Child child = (Child) parent;
child.childMethod(); // 문제 없음
Parent parent1 = new Parent();
Child child1 = (Child) parent1; //런타임 오류
child1.childMethod(); // 실행이 불가
}
}
실행 결과 ```
Child.childMethod
Exception in thread "main" java.lang.ClassCastException: class poly.basic.Parent
cannot be cast to class poly.basic.Child (poly.basic.Parent and poly.basic.Child
are in unnamed module of loader 'app')
at poly.basic.CastingMain4.main(CastingMain4.java:11
다운캐스팅이 가능한 경우

다운캐스팅이 불가능한 경우

Parent parent2 = new Parent()
new Parent()로 부모 타입으로 객체를 생성, 따라서 메모리 상에 자식 타입은 전혀 존재 X
생성 결과를 parent2에 담아 둔다, 이 경우 문제 발생X
Child child2 = (Child) parent2
parent2를 child 타입으로 다운캐스팅, 그런데 parent2는 Parent로 생성, 따라서 메모리 상에 Child 자체 존제 X
사용할 수 없는 타입으로 다운캐스팅하는 경우에 ClassCastException이라는 예외를 발생
Upcasting이 안전하고 DownCasting이 위험한 이유
업캐스팅의 경우 이런 문제 발생X, 왜냐면 객체를 생성하면 해당 타입의 상위 부모 타입은 모두 함께 생성, 따라서 위로만 타입을 변경하는 업캐스팅은 메모리상에 인스턴스가 모두 존재, 항상 안전
반면 다운캐스팅의 경우 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있다. 왜냐면 객체를 생성하면 부모 타입은 모두 함께 생성, 자식 타입은 생성X


A a = new B A로 업케스팅
B b = new B 자신과 같은 타입
C c = new B 하위 타입은 대입할 수 X, Compile Error
C c = (C) new B() 하위 타입으로 강제 다운 캐스팅, 하지만 B 인스턴스에 C와 관련된 부분X
-> 잘못된 casting
Instanceof
다형성에서 참조형 변수는 이름 그대로 다양한 자식을 대상으로 참조 가능, 어떤 인스턴스를 참조하고 있는지 확인하려면 ??
public class CastingMain5 {
public static void main(String[] args) {
Parent parent = new Parent();
System.out.println("parent1");
call(parent);
Parent parent1 = new Child();
System.out.println("parent2");
call(parent1);
}
private static void call(Parent parent){
parent.parentMethod();
if (parent instanceof Child) {
//Child 인스턴스인 경우 childMethod() 실행
System.out.println("child 인스턴스");
Child child = (Child) parent;
child.childMethod();
}
}
}
다운캐스팅을 수행하기 전에는 먼저 instanceof를 사용해서 원하는 타입으로 변경가능한지 확인
+) Pattern Matching for instanceof
public class CastingMain6 {
public static void main(String[] args) {
Parent parent = new Parent();
System.out.println("parent1");
call(parent);
Parent parent1 = new Child();
System.out.println("parent2");
call(parent1);
}
private static void call(Parent parent){
parent.parentMethod();
if (parent instanceof Child child) { //변수 선언 가능
System.out.println("child 인스턴스");
child.childMethod();
}
}
}
다형성과 메서드 오버라이딩
다형성을 이루는 또 하나의 중요한 핵심은 -> overriding
메서드 오버라이딩에서 꼭 기억해야 할 핵심은 오버라이딩 된 메서드가 항상 우선권을 갖는다.

Parent, Child 모두 value라는 같은 멤버 변수를 갖고 있다.
-> 멤버 변수는 오버라이딩 되지 않는다.
Parent, Child 모드 method라는 메서드를 소유, Child에서 메서드를 오버라이딩
-> 메서드는 오버라이딩 된다.

child 변수는 Child 타입, child.value, child.method()를 호출하면 인스턴스의 Child 타입에서 기능을 찾아서 실행

parent 변수는 Parent 타입. 따라서 parent.value, parent.method를 호출하면 인스턴스 Parent타입에서 기능을 찾아 실행

poly 변수는 Parent 타입. poly.value, poly.method를 호출하면 인스턴스의 Parent 타입에서 기능을 찾아서 실행
1. poly.value : Parent 타입에 있는 value 값을 읽는다.
2. poly.method() : Parent 타입에 있는 method를 실행. 그런데 하위 타입인 Child.method()가 오버라이딩 되어 있다. 오버라이딩 된 메서드는 항상 우선권을 갖는다. 따라서 Child.method가 실행
오버라이딩 된 메서드는 항상 우선권을 갖는다.
오버라이딩은 부모 타입에서 정의한 기능을 자식 타입에서 재정의. 만약 자식에서도 오버라이딩 하고 손자에서도 같은 메서드를 오버라이딩 하면 손자의 오버라이딩 메서드가 우선권을 소유