자바 기초 (10). 타입의 변환과 다형성
이번시간에는 타입의 변환과 다형성에 대해 알아보려 한다. 그중 내가 잘 모르는 다형성에대해 깊게 알아본다.
0. 들어가기전에
기본 타입과 마찬가지로 클래스도 타입의 변환이 있다. 이를 활용하면 객체 지향 프로그래밍의 다형성을 구현이 가능하다.
다형성 (Polymorphism) 이란? :
- 사용 방법은 동일하지만 다양한 객체를 활용하여 여러 실행 결과가 나오도록 하는 성질, (같은 타입인데 다른 실행결과를 나타낸다. )
- 메소드 재정의와 타입 변환으로 구현된다.
- 하나의 참조 변수로 여러 타입의 객체를 참조할 수 있는 것 ( 조상타입의 참조 변수로 자손 타입(상속 관계)의 객체를 다루는 것이 다형성의 기본개념)
1. 자동 타입 변환(promotion)
- 프로그램 실생 도중 말그대로 자동으로 타입 변환이 일어나는 것을 말한다.
부모타입 변수 = 자식타입;
Cat 클래스는 Animal 클래스의 상속을 받는다. 그때 아래와 같이 사용이 가능하다.
Cat cat = new Cat();
Animal animal = cat; // cat이 가지고 있는 번지가 animal 변수에 복사가 된다. 즉 animal 의 변수는 Cat을 참조하고 있다.
// 아래 코드는 위와 동일하다
// Animal animal = new Cat();
-
자동타입 변환은 바로 위 부모가 아니더라도 상송 계층에서 상위 타입인 경우 자동 타입 변환이 가능하다.
- 자동타입 변환에서 재정의 및 호출은 조금 헷갈릴 수 있다.
- 부모 타입으로 자동 타입 변환 이후에는 부모 클래스에 선언된 필드 및 메서드만 접근이 가능하다.
- 위의 예외로 메서드가 자식 클래서에서 재정의 될 경우 자식 클래스의 메서드가 대신 호출된다.
class Parent{
void method1(){
System.out.println("parent method1");
}
void method2(){
System.out.println("parent method2");
}
}
class Child extends Parent{
void method2(){
System.out.println("child method2");
}
void method3(){
System.out.println("child method3");
}
}
class ChildExample {
public static void main(String[] args){
Parent parent = new Child();
parent.method1();
parent.method2();
//parent.method3(); 사용 불가
}
}
위 출력결과는 아래와 같다.
parent method1
child method2
2. 필드의 다형성
- 필드 타입을 부모 타입으로 선언할 경우 다양한 자식 객체가 저장되어 필드 사용 결과가 달라질수 있다.
복잡한 예제 하나만 보고 가자.. 뭔가 예제를 만들려고 했는데 너무 꼬인듯 하다. 우리가 FPS게임을 하다보면 총을 람보처럼 3,4개씩 들고 다닌다 아래는 그러한 예제를 만들어 보았다.
package polymorphism;
public class Weapon {
public String name;
public int bulletType;
public int maxBulletCapacity;
public int accumulatedShot;
public Weapon(String name, int bulletType, int maxBulletCapacity) {
this.name = name;
this.bulletType = bulletType;
this.maxBulletCapacity = maxBulletCapacity;
}
public boolean fire() {
pullTrigger();
if(accumulatedShot <= maxBulletCapacity) {
gunFire();
return true;
}else {
System.out.println("총알이 부족합니다.");
return false;
}
}
public void pullTrigger() {
accumulatedShot++;
}
public void gunFire() {
System.out.println(name + " fire!! " + name + "의 남은 총알의 수는" + (maxBulletCapacity - accumulatedShot) + "발 입니다.");
}
}
package polymorphism;
public class Army {
Weapon firstWeapon = new Weapon("기존 주무기", 9, 5);
Weapon secondWeapon = new Weapon("기존 보조무기", 9, 5);
Weapon pistol = new Weapon("기존 권총", 12, 1);
int shot() {
System.out.println("=================" + "!" + " 사수 사격 계시 =================");
if(!firstWeapon.fire()) {stop(firstWeapon.name); return 1;}
if(!secondWeapon.fire()) {stop(secondWeapon.name); return 2;}
if(!pistol.fire()) {stop(pistol.name); return 3;}
return 0;package polymorphism;
public class Weapon {
public String name;
public int bulletType;
public int maxBulletCapacity;
public int accumulatedShot;
public Weapon(String name, int bulletType, int maxBulletCapacity) {
this.name = name;
this.bulletType = bulletType;
this.maxBulletCapacity = maxBulletCapacity;
}
public boolean fire() {
pullTrigger();
if(accumulatedShot <= maxBulletCapacity) {
gunFire();
return true;
}else {
System.out.println("총알이 부족합니다.");
return false;
}
}
public void pullTrigger() {
accumulatedShot++;
}
public void gunFire() {
System.out.println(name + " fire!! " + name + "의 남은 총알의 수는" + (maxBulletCapacity - accumulatedShot) + "발 입니다.");
}
}
}
void stop(String name) {
System.out.println("현재 " + name + " 는 재장전이 필요합니다.");
}
}
package polymorphism;
public class K1 extends Weapon{
public K1(String name, int bulletType, int maxBulletCapacity) {
super(name, bulletType, maxBulletCapacity);
}
public boolean fire() {
pullTrigger();
if(accumulatedShot < maxBulletCapacity) {
gunFire();
return true;
}else {
System.out.println("K1 총알이 부족합니다.");
return false;
}
}
}
package polymorphism;
public class K2 extends Weapon{
public K2(String name, int bulletType, int maxBulletCapacity) {
super(name, bulletType, maxBulletCapacity);
}
public boolean fire() {
pullTrigger();
if(accumulatedShot < maxBulletCapacity) {
gunFire();
return true;
}else {
System.out.println("K2 총알이 부족합니다.");
return false;
}
}
}
package polymorphism;
public class M9 extends Weapon{
public M9(String name, int bulletType, int maxBulletCapacity) {
super(name, bulletType, maxBulletCapacity);
}
public boolean fire() {
pullTrigger();
if(accumulatedShot < maxBulletCapacity) {
gunFire();
return true;
}else {
System.out.println("M9 총알이 부족합니다.");
return false;
}
}
}
package polymorphism;
public class ArmyExample {
public static void main(String[] args) {
Army soldier = new Army();
for (int i=1; i<=65; i++) {
int problemWeapon = soldier.shot();
switch(problemWeapon) {
case 1 :
System.out.println("주무기 K1 탄창교체");
soldier.firstWeapon = new K1("K1", 9, 40);
break;
case 2 :
System.out.println("주무기 K2 탄창교체");
soldier.secondWeapon = new K2("K2", 9, 25);
break;
case 3 :
System.out.println("주무기 M9 탄창교체");
soldier.pistol = new M9("M9", 12, 9);
break;
default :
break;
}
System.out.println("=================" + i + " 사수 사격 준비 =================");
}
}
}
출력 결과
=================! 사수 사격 계시 =================
기존 주무기 fire!! 기존 주무기의 남은 총알의 수는4발 입니다.
기존 보조무기 fire!! 기존 보조무기의 남은 총알의 수는4발 입니다.
기존 권총 fire!! 기존 권총의 남은 총알의 수는0발 입니다.
=================1 사수 사격 준비 =================
=================! 사수 사격 계시 =================
기존 주무기 fire!! 기존 주무기의 남은 총알의 수는3발 입니다.
기존 보조무기 fire!! 기존 보조무기의 남은 총알의 수는3발 입니다.
총알이 부족합니다.
현재 기존 권총 는 재장전이 필요합니다.
주무기 M9 탄창교체
=================2 사수 사격 준비 =================
=================! 사수 사격 계시 =================
기존 주무기 fire!! 기존 주무기의 남은 총알의 수는2발 입니다.
기존 보조무기 fire!! 기존 보조무기의 남은 총알의 수는2발 입니다.
M9 fire!! M9의 남은 총알의 수는8발 입니다.
=================3 사수 사격 준비 =================
=================! 사수 사격 계시 =================
기존 주무기 fire!! 기존 주무기의 남은 총알의 수는1발 입니다.
기존 보조무기 fire!! 기존 보조무기의 남은 총알의 수는1발 입니다.
M9 fire!! M9의 남은 총알의 수는7발 입니다.
=================4 사수 사격 준비 =================
=================! 사수 사격 계시 =================
기존 주무기 fire!! 기존 주무기의 남은 총알의 수는0발 입니다.
기존 보조무기 fire!! 기존 보조무기의 남은 총알의 수는0발 입니다.
M9 fire!! M9의 남은 총알의 수는6발 입니다.
=================5 사수 사격 준비 =================
일단.. 너무 이상하게 만들어진것 같아 죄송… 좀 잘만들어서 진짜 같이 만들고 싶었는데,,
일단!!!
설명하고 싶었던건 총알을 다쓰면 재장전 하고, weapon을 다쓰면 Weapon자신을 다시 사용하는 것이 아닌 상속 받은 각각의 무기(K1,K2,M9)을 사용하여 교체 시킨다는 것이다. 즉 부모클래스 Weapon의 타입에 자식 클래스 인스턴스를 대입 받는 다는것이다.
이제 교체된 Weapon에선 새로 대입된 자식클래스들의 fire()메서드가 작동된다.
각각의 자식 클래스의 fire()메서드를 오버라이딩 하여 재정의 됬기 때문에 더이상 부모의 fire()메서드가 동작 하지 않고 재정의된 fire()가 호출되는것을 볼수 있다.
이게 바로 __필드의 다형성__이다.
3. 매개변수의 다형성
- 매개 변수를 부모 타입으로 선언하는 효과
- 메서드 호출시 매개값으로 부모 객체 및 모든 자식 객체를 제공할 수 있다. 자식의 재정의된 메서드가 호출 (다형성)
class Vehicle
public class Vehicle{
public void run(){
System.out.println("차량이 달린다~~.");
}
}
class Driver
public class Driver{
public void driver(Vehicle vehicle){
vehicle.run();
}
}
class DriverExample
public class DriverExample{
public static void main(String[] args){
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.run(vehicle);
}
}
출력결과
차량이 달린다~~.
위 예제를 이걸 매개변수 다형성을 사용하여 확인해보자 class Bus
public class Bus{
public void run(){
System.out.println("버스차량이 달린다~~.");
}
}
class Taxi
public class Taxi{
public void run(){
System.out.println("택시차량이 달린다~~.");
}
}
DriverExample
public class DriverExample{
public static void main(String[] args){
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.run(bus);
driver.run(taxi);
}
}
실행결과
버스차량이 달린다~~.
택시차량이 달린다~~.
결과를 보면 Driver class 에 drive라는 메서드는 Vehicle을 매개변수로 사용하는것은 똑같지만 DriverExample에서 어떤 매서드를 사용하였는지에 따라 결과가 다른 값을 반환하는걸 확인할 수 있다.
4. 강제 타입 변환 (Casting)
- 기본타입의 Casting도 있지만 클래스간의 Casting도 있다.
- 부모 타입을 자식 타입으로 변환하는 과정
- 조건 : 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 반대로 변환할 때 사용한다.
자식타입 변수 = (자식타입) 부모타입;
자바 코드로 보면
Parent parent = new Child();
// Child child = parent; 불가
Child child = (Child) parent;
// 위의 조건을 볼때 아래 같은 캐스팅은 불가하다.
Parent parent2 = new Parent();
// Child child2 = (Child) parent2;
그러면 위와 같은 불편한 강제 타입 변환은 왜 사용하는것일까? 이 포스트 __(1번 자동타입변환)__을 보면 알수 있다.
아래 코드를 통해 다시 설명하겠다. class Parent, class Child
class Parent{
void method1(){}
void method2(){}
}
class Child{
void method2(){}
void method3(){}
}
class ParentExample
public class ParentExample{
public static void main(String [] args){
Parent parent = new Child();
parent.method1();
parent.method2();
//parent.method3(); 불가 하다.
//그럼 위의 코드를 어떻게 사용할 수 있을까?
Child child = (Child) parent;
child.method3();
}
}
5. 객체 타입 확인
- 어떤 객체가 어느 클래스의 인스턴스인지 확인
- 메소드 내 강제 타입 변환 필요한 경우
- 타입 확인하지 않고 강제 타입 변환 시도 시 ClassCastException 발생할수 있기때문에 확인해야한다.
- instanceof 연산저를 통해 확인 한다.
boolean result = 좌향(객체) instanceof 우향(타입)
좌향이 우향에서 만들어진 객체인지에 따라 true, false를 return 해준다, 아래와 같은 객체를 걸러?? 내는데 사용된다
Parent parent = new Parent();
Child child = (Child) parent; // ClassCastException 발생
그래서 아래와 같이 응용하여 사용된다.
public void method(Parent parent){
if(parent instanceof Child){
Child child = (Child) parent;
}
}