다형성은 객체 지향 언어의 특징 중 하나로, 하나의 객체를 다양한 형태로 이용할 수 있는 특징이다. 참조 변수 하나로 상속 관계에 있는 여러 타입 객체를 참조할 수 있다.
자식 클래스의 객체를 부모 타입으로 형변환하는 것을 의미한다. 자식 클래스의 객체를 통해 부모 클래스처럼 사용할 수 있다.
상속 관계에서는 부모 타입의 참조형 변수가 모든 자식 타입 객체의 주소를 참조할 수 있다.
public class JazzBand{
private int numberOfMember;
private String bandName;
private String leaderPosition;
JazzBand(){} //기본생성자
JazzBand(int numberOfMember, String bandName,
String leaderPosition){
this.numberOfMember = numberOfMember;
this.bandName = bandName;
this.leaderPosition = leaderPosition;
}//매개변수 생성자
@Override
public String toString(){
return numberOfMember + ", " + bandName + ", " +
leaderPosition;
}
=====================================================
}
public class BigBand extends JazzBand{
private String conductor;
BigBand(){}
BigBand(int numberOfMember, String bandName,
String leaderPosition, String conductor){
super(numberOfMember, bandName, leaderPosition);
this.conductor = conductor;
@Override
public String toString(){
return super.toString + ", " + conductor;
}
}
====================================================
}
public class FusionBand extends JazzBand{
private int numberOfElectricInst;
FusionBand(){}
FusionBand(int numberOfMember, String bandName,
String leaderPosition, int numberOfElectricInst){
super(numberOfMember, bandName, leaderPosition);
this.numberOfElectricInst = numberOfElectricInst;
}
@Override
public String toString(){
return super.toString + ", " + numberOfElectricInst;
}
// getter / setter는 생략한다.
//(존재한다고 가정)
위와 같이 부모 클래스와 이를 상속받는 두 개의 자식 클래스를 만들어서 사용할 것이다.
JazzBand jb1 = new JazzBand();
JazzBand jb2 = new BigBand(); //업캐스팅
JazzBand jb3 = new FusionBand(); //업캐스팅
BigBand bb1 = new BigBand();
FusionBand fb1 = new FusionBand();
- 부모타입의 참조변수 = 자식 객체 == (업캐스팅)
위와 같이이 업캐스팅된 사례와 일반적으로 변수를 생성한 경우가 있다고 했을 때,
자식 객체가 부모 객체로 변한 것이므로, 자식 객체 고유의 필드와 메소드를 사용할 수 없다.
jb2.setConductor("MrNam"); // 실행 불가
bb1.setConductor("MrNam"); // 실행 가능
만약 업캐스팅된 변수에서 자식 객체 고유의 필드나 메소드에 접근하기 위해서는 다운캐스팅이 필요하다.
부모타입의 참조변수가 자식 객체를 참조하는 업캐스팅 상태에서, 만약 참조변수가 본래 자식 객체의 필드와 메소드에 접근하려고 할 때, "강제형변환" 을 통해서 본래의 필드와 메소드를 사용하는 방법
BigBand bb2 = (BigBand)jb2;
//얕은 복사를 통해 다운캐스팅해주었다.
// * 효율적인 다운캐스팅 방법!
bb2.setConductor("Scotfield");
System.out.println(((BigBand)jb2).getConductor());
//다운캐스팅을 이용해 자식 객체의 메소드를 호출
//주의!
//"." 연산자가 형변환 연산자보다 우선순위가 높음
다운 캐스팅을 할 때 자식 객체끼리 강제형변환을 하는 등 잘못된 방법으로 수행 시 java.lang.ClassCastException에러가 발생하게 된다.
이를 방지하기 위해 instanceof 연산자를 사용한다.
instanceof 연산자
참조형 변수가 어떤 클래스를 참조하는지 확인하는 연산자로,
참조하는 타입이 맞으면 true, 아니면 false를 반환한다.
JazzBand jb1 = new Bigband();
if(jb1 instanceof FusionBand) {
FusionBand fb1 = (FusionBand)jb1; // 맞을 시 다운캐스팅
System.out.println("성공");
}else {
System.out.println("실패(FusionBand 타입이 아님)");
}
위와 같이 instanceof 연산자를 사용해서 참조타입이 맞는지 확인 후 다운캐스팅을 해준다.
바인딩은 실제 실행할 메소드 코드와 호출하는 코드를 연결하는 것을 의미한다.
프로그램 실행 전 컴파일 단계에서 메소드와 메소드 호출부를 연결하는 것이다.
JazzBand jb1= new JazzBand(3, "킴스트리오", "베이스");
jb1.getBandName();
위와 같이 실행했을 때 컴파일러는 getBandName()가 JazzBand 클래스 내의 메소드임을 인지하고 프로젝트의 패키지 내 클래스에서 그 값을 가져온다. 이를 정적 바인딩이라고 한다.
컴파일 시 정적 바인딩된 메소드를 실행할 당시의 객체 타입 기준으로 바인딩 되는 것이다.
JazzBand jb1 = new FusionBand(5, "텀블러즈", "키보드", 4);
jb1.toString();
jb1은 FusionBand으로 업스캐딩된 상태이며, 따라서 부모 객체의 toString() 메소드가 실행될 것 같지만, 실제로는 자식 객체에 오버라이딩된 메소드가 우선적으로 수행된다.
5, 텀블러즈, 키보드, 4
동적 바인딩에 의해 이와 같이 실행된다.