21. 다형성

Isaiah IM·2023년 7월 25일
0

java basic

목록 보기
23/38
post-thumbnail

1. 다형성

※다형성은 상속과 깊은 연관이 있으므로 반드시 상속에 대한 높은 이해가 필요하다.

1.1 다형성이란?

다형성이란 한 타입의 참조변수로 여러 객체를 참조할 수 있는 기능을 말한다.

우리가 앞서 작성했었던 자동차 제어 클래스를 생각해보자. 우리가 자동차 제어 클래스를 기반으로 아래와 같이 구급차와 소방차를 제어하는 클래스를 상속했다고 가정하자.

자동차

public class Car {
	private boolean startupState;
	private float speed;
	
	{// 초기화 블럭
		this.startupState=false;
		this.speed=0.0f;
	}
	
	Car(){}
	
	Car(float speed){
		this.speed=speed;
	}
	
	
	public void startUp() {// 시동걸기
		this.startupState=true;
		System.out.println("시동걸림.");
	}
	
	public void drive(float speed) {// 이동
		this.speed=speed;
		System.out.println("속도: "+this.speed+"km");
	}
	
	public void stop() {// 브레이크
		this.speed=0.0f;
		this.startupState=false;
		System.out.println("시동끔.");
	}
	
	public void horn() {// 경적
		System.out.println("빵빵!! 피자빵!! 초코빵!! 크림빵!!");
	}
}

구급차

public class Ambulance extends Car{
	private boolean startupState;
	private float speed;
	private boolean emergencyState;
	
	@Override
	public void startUp() {// 시동걸기
		this.startupState=true;
		System.out.println("구급차 시동걸림.");
	}
	
	@Override
	public void drive(float speed) {// 이동
		this.speed=speed;
		System.out.println("구급차 속도: "+this.speed+"km");
	}
	
	@Override
	public void stop() {// 브레이크
		this.speed=0.0f;
		this.startupState=false;
		System.out.println("구급차 시동끔.");
	}
	
	@Override
	public void horn() {// 경적
		
		if(this.emergencyState==true) {
			System.out.println("삐용삐용!! 긴급상황!!!");
		}
		else {
			System.out.println("빵빵!!");
		}
		
	}
	
	public void emergencyMode(boolean state) {// 긴급모드
		this.emergencyState=state;
	}
}

소방차

public class FireTruck extends Car{
	private boolean startupState;
	private float speed;
	private boolean emergencyState;
	
	@Override
	public void startUp() {// 시동걸기
		this.startupState=true;
		System.out.println("소방차 시동걸림.");
	}
	
	@Override
	public void drive(float speed) {// 이동
		this.speed=speed;
		System.out.println("소방차 속도: "+this.speed+"km");
	}
	
	@Override
	public void stop() {// 브레이크
		this.speed=0.0f;
		this.startupState=false;
		System.out.println("소방차 시동끔.");
	}
	
	@Override
	public void horn() {// 경적
		
		if(this.emergencyState==true) {
			System.out.println("삐용삐용!! 긴급상황!!!");
		}
		else {
			System.out.println("빵빵!!");
		}
		
	}
	
	public void emergencyMode(boolean state) {// 긴급모드
		this.emergencyState=state;
	}
	
	public void fireWaterCanon() {// 물대포 발사
		System.out.println("물대포 발사!!!");
	}
}

이를 각각 제어하고자 하면 다음과 같이 코드를 작성할 수 있다.

public class Main {
	public static void main(String[] args) {
		Car car = new Car();
		Ambulance amb = new Ambulance();
		FireTruck fire = new FireTruck();
		
		/*시동걸기*/
		car.startUp();
		amb.startUp();
		fire.startUp();
		
		/*이동*/
		car.drive(50);
		amb.drive(50);
		fire.drive(50);
		
		
		amb.emergencyMode(true);
		fire.emergencyMode(true);
		
		car.horn();
		amb.horn();
		fire.horn();
		
		fire.fireWaterCanon();
		
		car.stop();
		amb.stop();
		fire.stop();
	}
}

output

시동걸림.
구급차 시동걸림.
소방차 시동걸림.
속도: 50.0km
구급차 속도: 50.0km
소방차 속도: 50.0km
빵빵!! 피자빵!! 초코빵!! 크림빵!!
삐용삐용!! 긴급상황!!!
삐용삐용!! 긴급상황!!!
물대포 발사!!!
시동끔.
구급차 시동끔.
소방차 시동끔.

이와같이 각자 클래스를 제어할 수 있다.

그런데 한번 생각해보자. 소방차와 구급차도 결국에는 자동차 아닌가? 뿐만 아니라 각각의 내부의 동작 매커니즘은 다를 수 있지만 상당부분을 부모클래스인 자동차 클래스의 메소드 이름이 동일하다. 그렇다면 이를 "자동차"로 묶어서 한번에 관리한다면 더 편리하지 않겠는가? 이것을 가능하게 하는 것이 바로 다형성이다.
소방차도, 구급차도 결국에는 자동차이기에 자동차를 관리하는 클래스로 묶어서 한번에 관리하면 개발의 편의성이 높아진다는 것이 다형성의 핵심이다. 조금 더 확장하면 아래 그림과 같이 하나의 클래스로 인스턴스들을 만들고 drive();메소드를 호출하면 모두 달릴 수 있게 하는 것이다.

1.2 다형성과 상속

상속과 다형성은 깊은 관계가 있다. 위에서 자동차로 언급한 것과 같이 구급차도, 소방차도 결국에는 자동차이기 때문에 자동차로 클래스를 묶는것을 프로그램으로 표현하면 상속관계에서 부모클래스의 참조변수로 자식클래스의 인스턴스를 참조할 수 있도록 하는것 이다.
말이 어려우니 직접 코드로 작성하면 다음과 같다.

public class Main {
	public static void main(String[] args) {
		Car car = new Car();
		Car amb = new Ambulance();// 부모클래스의 참조변수로 자식클래스의 인스턴스를 참조
		Car fire = new FireTruck();// 부모클래스의 참조변수로 자식클래스의 인스턴스를 참조
		
		/*시동걸기*/
		car.startUp();
		amb.startUp();
		fire.startUp();
		
		/*이동*/
		car.drive(50);
		amb.drive(50);
		fire.drive(50);
		
		/*경적*/
		car.horn();
		amb.horn();
		fire.horn();
		
		/*시동 끄기*/
		car.stop();
		amb.stop();
		fire.stop();
	}
}

output

시동걸림.
구급차 시동걸림.
소방차 시동걸림.
속도: 50.0km
구급차 속도: 50.0km
소방차 속도: 50.0km
빵빵!! 피자빵!! 초코빵!! 크림빵!!
빵빵!!
빵빵!!
시동끔.
구급차 시동끔.
소방차 시동끔.

놀랍게도 이렇게 인스턴스를 생성할때 참조변수가 달라도 인스턴스가 생성이 된다. 그러나, ambfire같은 경우 결국에는 Car클래스로 선언이 됬기 때문에 Car클래스의 인스턴스에서 오버라이딩된 메소드만 사용이 가능하다.

또한 이러한 경우는 아래 그림과 같이 상속관계에서만 가능하다. 즉, 상속관계가 아니면 이러한 다형성은 불가능하다.

1.3 up casting과 down casting

up casting이란 자식클래스에서 부모클래스로 형변환이 되는 경우이며, down casting이란 부모클래스에서 자식클래스로 형변환이 되는 경우이다.

up casting

up casting은 자식클래스를 부모클래스로 형변환(캐스팅, casting)되는 것으로, 자동으로 형변환이 되기 때문에 형변환 명시를 하지 않아도 된다.

Car car;
Ambulance amb = new Ambulance();

car=amb;// up casting

이때, up casting된 경우 결국 부모클래스 자료형으로 선언이 됬기 때문에 부모클래스에서 오버라이딩된 인스턴스만 사용이 가능하다.

Car car;
Ambulance amb = new Ambulance();

car=amb;// up casting

car.emergencyMode(true);// 컴파일 오류! 자식클래스의 고유 메소드 사용 불가!

이러한 up casting의 메모리 구조를 그림으로 나타내면 다음과 같다.

down casting

down casting은 부모클래스를 자식클래스로 형변환(캐스팅, casting)되는 것으로, 반드시 형변환 명시를 해야 한다.

Car car = new Ambulance();// up casting
Ambulance amb;

amb=(Ambulance)car;// down casting, 반드시 형변환 명시!!

이때, down casting된 경우 결국 자식클래스 자료형으로 선언이 됬기 때문에 자식클래스의 모든 인스턴스를 사용할 수 있다.

Car car = new Ambulance();// up casting
Ambulance amb;

amb=(Ambulance)car;// down casting

amb.emergencyMode(true);// 사용 가능

이러한 down casting의 메모리 구조를 그림으로 나타내면 다음과 같다.

위 그림에서 amb는 car를 가리키고, car는 결국 Ambulance 인스턴스를 가리키기 때문에 결국 다음 그림과 같은 구조가 된다.

up castingdown casting은 본질적으로 참조변수의 자료형을 변환하는 것이다. 즉, 참조변수의 자료형만 변화시켜서 하나의 자료형으로 여러 인스턴스에 접근을 하는 것이다. 다시 말하면 자료형이 바뀌는 것이지 인스턴스가 바뀌는 것이 아니다.

조금 더 쉽게 예를 들면 파스타를 김치통에 담는다고 해서 파스타가 김치가 되는 것이 아니다. 뚜껑 닫고 들고다니면 김치로 오해는 할 수 있어도 그저 김치통에 담긴 파스타가 될 뿐이다.

up castingdown casting 역시 마찬가지다.
new키워드로 인스턴스를 생성한 것이 파스타라면, up castingdown casting은 김치통과 같다.
김치통 안에 파스타를 넣고 뚜껑을 닫으면 안에 감치가 들어있겠지..라고 오해하듯 up casting 혹은 down casting을 통해 사용하는 인스턴스의 범위는 조절할 수 있어도 결국 김치통에 들어있는것은 파스타인것 처럼 아무리 up casting 혹은 down casting을 해도 결국 new키워드로 생성한 것이 본질이다.

다형성은 파스타를 김치통, 밥솥, 라면냄비에 넣어 안에 ~~가 들어있겠지라고 페이크 줘서 파스타 하나로 여러 음식을 갖고있는것 처럼 보이게 하는 것과 같다. 마치 인스타 재무설계사 마냥 실제로는 ㅈ도 없는데 있어보이게 허세부려서 영업하는것과 같다.

1.4 instanceof

instanceof연산자는 인스턴스의 실제 자료형을 알아보는 연산자로 연산의 결과로 boolean 값을 반환하며, 주로 조건문에 사용된다.

if(참조변수 instanceof 실제_자료형){

}

instanceof연산자는 참조변수가 실제로 참조하고 있는 인스턴스의 자료형을 확인할때 사용하며, true 혹은 false를 반환한다. 이때, true를 반환했다는 것은 참조변수가 검사한 자료형의 인스턴스로 변환이 가능하다는 것을 의미한다 즉, true를 반환했다고 해서 무조건 해당 자료형이라는 것은 아니다.

public class Main {
	public static void main(String[] args) {
		Car car = new FireTruck();
		
		if(car instanceof Car) {
			System.out.println("car 인스턴스는 Car 로 형 변환이 가능합니다.");
		}
		if(car instanceof FireTruck) {
			System.out.println("car 인스턴스는 FireTruck 로 형 변환이 가능합니다.");
		}
		if(car instanceof Ambulance) {
			System.out.println("car 인스턴스는 Ambulance 로 형 변환이 가능합니다.");
		}
		
	}
}

output

car 인스턴스는 Car 로 형 변환이 가능합니다.
car 인스턴스는 FireTruck 로 형 변환이 가능합니다.

위 결과를 보면 car참조변수의 인스턴스는 FireTruck으로, CarFireTruck으로 형변환이 가능하다. 즉, instanceof연산자에서 true를 반환했다고 해서 무조건 검사한 자료형이라는 보장은 없다.
Car의 경우 FireTruck클래스의 부모클래스로, down casting으로 형변환이 가능하며, FireTruckcar의 실제 인스턴스 이므로 형변환이 가능하다.


2. 다형성의 활용

2.1 여러 객체를 배열로 한번에 관리하기

다형성을 이용하면 여러 객체들을 배열을 이용해 한번에 관리할 수 있다.

지금까지 배운 다형성 내용을 활용해보자.
다형성과 이용하면 다음과 같이 코드를 작성할 수 있다.

public class Main {
	public static void main(String[] args) {
		
		Car car1= new Car();
		Car car2= new Ambulance();
		Car car3= new FireTruck();
		
	}
}

이를 배열을 이용하면 다음과 같다.

public class Main {
	public static void main(String[] args) {
		
		Car[] cars= {
			new Car(),
			new Ambulance(),
			new FireTruck()
		};
		
	}
}

이제 이 자동차들을 시동을 걸고 동작시키면 다음과 같다.

public class Main {
	public static void main(String[] args) {
		
		Car[] cars= {
			new Car(),
			new Ambulance(),
			new FireTruck()
		};
		
		/*동작*/
		for(Car car: cars) {
			car.startUp();// 자동차 시동걸기
			car.drive(50);// 자동차 움직이기
		}
		
	}
}

output

시동걸림.
속도: 50.0km
구급차 시동걸림.
구급차 속도: 50.0km
소방차 시동걸림.
소방차 속도: 50.0km

위 코드를 보면 반복문에서 Car->Ambulance->FireTruck순으로 시동이 걸리고 동작하는것을 알 수 있다.

다음으로 자동차에 화재가 났다고 가정하고 자동차를 멈추고, 소방차와 구급차는 각각 물대포와 사이렌을 울리는 코드를 작성하면 다음과 같다.

public class Main {
	public static void main(String[] args) {
		
		Car[] cars= {
			new Car(),
			new Ambulance(),
			new FireTruck()
		};
		
		/*동작*/
		for(Car car: cars) {
			car.startUp();// 자동차 시동걸기
			car.drive(50);// 자동차 움직이기
		}
		
		/*긴급상황*/
		for(Car car: cars) {
			if(car instanceof Ambulance) {// 구급차
				((Ambulance)car).emergencyMode(true);
				car.horn();
			}
			else if(car instanceof FireTruck) {// 소방차
				((FireTruck)car).emergencyMode(true);
				((FireTruck)car).fireWaterCanon();
				car.horn();
			}
			else// 일반 자동차
			{
				car.stop();
			}
			
		}
	}
}

output

시동걸림.
속도: 50.0km
구급차 시동걸림.
구급차 속도: 50.0km
소방차 시동걸림.
소방차 속도: 50.0km
시동끔.
삐용삐용!! 긴급상황!!!
물대포 발사!!!
삐용삐용!! 긴급상황!!!

위 코드를 보면 instanceof연산자를 활용해 인스턴스를 판별하는 것을 알 수 있다.

이와같이 다형성을 이용하면 한번에 여러 클래스를 쉽게 관리할 수 있다.

profile
나는 생각한다. 고로 나는 코딩한다.

0개의 댓글