본문 바로가기
프로그래밍언어/Java

07-2 타입 변환과 다형성

by 스꼬맹이브로 2021. 3. 9.
728x90
반응형
SMALL

다형성이란 동일하지만 다양한 객체를 이용해서 다양한 실행 결과가 나오도록 하는 성질을 나타낸다.

예를 들어 자동차가 타이어를 사용하는 방법은 동일하지만 어떤 타이어를 사용(장착)하느냐에 따라 주행 성능이 달라질 수 있음을 말할 수 있다.

자동차 타이어를 통한 다형성 예시

다형성을 구현하려면 메소드 재정의와 타입 변환이 필요하다.

 

<자동 타입 변환>

타입 변환이란 타입을 다른 타입으로 변환하는 행위를 말한다.

기본 타입의 변환은 이미 앞에서 다뤘는데, 클래스도 마찬가지로 타입 변환을 할 수 있다.

클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생하며, 자식은 부모 타입으로 자동 타입 변환이 가능하다.

자동타입변환(promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.

자동 타입 변환의 조건은 다음과 같다.

자동 타입 변환의 개념은 '자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다' 는 것이다.

예를 들어, 강아지가 동물의 특징과 기능을 상속받았다면 '강아지는 동물이다.' 가 성립한다.

 

 

또 위의 그림과 같이 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.

D 객체는 B와 A 타입으로 자동 타입 변환이 될 수 있고, E객체는 C와 A 타입으로 자동 타입 변환이 될 수 있다.

그러나 D 객체는 C타입으로 변환될 수 없으며 마찬가지로 E객체는 B타입으로 변환될 수 없다.

//A클래스
public class A {

}
//B클래스
public class B extends A {

}
//C클래스
public class C extends A {

}
//D클래스
public class D extends B {

}
//E클래스
public class E extends C {

}
public class PromotionExample {
	public static void main(String[] args) {
		B b = new B();
		C c = new C();
		D d = new D();
		E e = new E();		
		
		A a1 = b;
		A a2 = c;
		A a3 = d;
		A a4 = e;
		
		B b1 = d;
		C c1 = e;

		//B b3 = e;
		//C c2 = d;
        //16, 17번 라인은 상속 관계에 있지 않기 때문에 컴파일 에러 발생
	}
}

부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.

비록 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된다.

그러나 예외가 있는데, 메소드가 자식 클래스에서 재정의되었다면 자식 클래스의 메소드가 대신 호출된다. 이것은 다형성과 관련이 있이 때문에 매우 중요한 성질이므로 잘 알아두자!

 

//부모클래스
public class Parent {
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}
//자식클래스
public class Child extends Parent {
	@Override
	public void method2() {
		System.out.println("Child-method2()");
	}
	
	public void method3() {
		System.out.println("Child-method3()");
	}
}
public class ChildExample {
	public static void main(String[] args) {
		  Child child = new Child();

		  Parent parent = child;

		  parent.method1();

		  parent.method2();

		  //parent.method3();  (호출 불가능)
	}
}

결과:

child 객체는 method3()메소드를 가지고 있지만 parent 타입으로 변환되었기 때문에 method3()을 호출할 수없다.

그러나 method2()는 parent와 child 둘 다 가지고 있고, 재정의된 메소드이기 때문에 자식 메소드에서 호출된다.

 

<매개 변수의 다형성>

메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수 있다.

 

먼저,  부모 클래스인 Vehicle클래스는 다음과 같다.

public class Vehicle {
	public void run() {
		System.out.println("차량이 달립니다.");
	}
}

다음은 Driver 클래스인데, drive()메소드에서 Vehicle 타입의 매개값을 받아서 run()메소드를 호출한다.

public class Driver {
	public void drive(Vehicle vehicle) {
		vehicle.run();
	}
}

다음은 자식 클래스인 Bus와 Taxi클래스이다. 이 클래스에서 run() 메소드를 재정의한다.

public class Bus extends Vehicle {
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}
public class Taxi extends Vehicle {
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}
}

이제 위의 클래스들을 사용하여 실행하는 DriverExample클래스를 보자.

public class DriverExample {
	public static void main(String[] args) {
		Driver driver = new Driver();
		
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		
		driver.drive(bus); //자동타입변환 : Vehicle vehicle = bus;
		driver.drive(taxi); //자동타입변환 : Vehicle vehicle = taxi;
	}
}

결과 :

결과로 "차량이 달립니다."가 아닌 재정의한 값이 나온 것을 확인할 수 있다.

이와 같이 매개값의 자동 타입 변환과 메소드 재정의를 이용해서 매개 변수의 다형성을 구현할 수 있다.

 

<강제 타입 변환>

강제 타입 변환(casting)은 부모 타입을 자식 타입으로 변환하는 것을 말한다.

모든 부모 타입을 자식 타입으로 강제 변환할 수 있는 것은 아니다. 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.

예를 들어 밑 코드와 같이 child 객체가 parent 타입으로 자동 변환된 상태에서 원래 child로 강제 변환할 수 있다.

Parent parent = new Child(); //자동 타입 변환
Child child = (Child) parent; //강제 타입 변환

자식 타입이 부모 타입으로 자동 타입 변환하면 부모에 선언된 필드와 메소드만 사용이 가능하다는 제약 사앙이 따른다.

만약 자식에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환한 다음 자식 필드와 메소드를 사용하면 된다.

 

public class Parent {
	public String field1;
	
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}
public class Child extends Parent {
	public String field2;
	
	public void method3() {
		System.out.println("Child-method3()");
	}
}

부모클래스와 자식클래스가 다음과 같이 선언되어 있을 때 자동 타입 변환과 강제 타입 변환을 해보자.

public class ChildExample {
	public static void main(String[] args) {
		Parent parent = new Child();
		System.out.println("child를 parent로 자동 타입 변환");
		parent.field1 = "data1";
		parent.method1();
		parent.method2();
		/*
		parent.field2 = "data2";  //(불가능)
		parent.method3();         //(불가능)
		*/
		System.out.println("parent.field1 = "+parent.field1);
		System.out.println("-------------------------------");
		Child child = (Child) parent;
		System.out.println("parent를 child로 강제 타입 변환");
		child.field2 = "yyy";  //(가능)
		child.method3();     //(가능)
		System.out.println("child.field2 = "+child.field2);
	}
}

결과 :

강제 타입 변환 전 사용 불가능했던 필드 field2와 메소드 method3()을 강제 타입 변환 후 사용가능한 것을 확인할 수 있다.

 

<객체 타입 확인>

강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에 다음과 같이 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환할 수 없다.

Parent parent = new Parent();
Child child = (Child) parent; //강제 타입 변환 불가능

그렇기 때문에 자바에서는 어떤 객체가 어떤 클래스의 인스턴스인지 확인할 수 있는 방법을 제공한다.

확인하는 방법은 instanceof 연산자를 사용하면된다.

instanceof 연산자의 좌항에는 객체가 오고 우항에는 타입이 오는데, 좌항의 객체가 우항의 인스턴스이면 true를 리턴하고 그렇지 않으면 false를 리턴한다.

boolean result = 좌항(객체) instanceof 우항(타입)

instanceof 연산자는 주로 매개값의 타입을 조사할 때 사용되며 메소드 내에서 강제 타입 변환이 필요한 경우 반드시 매개값이 어떤 객체인지 instanceof 연산자로 확인하고 안전하게 강제 타입 변환을 하는 것이 바람직하다.

 

만약 타입을 확인하지 않고 강제 타입 변환을 시도한다면 ClassCastException이 발생할 수 있다.

public class Parent {
}
public class Child extends Parent {
}
public class InstanceofExample {
	public static void method1(Parent parent) {
		if(parent instanceof Child) {
			Child child = (Child) parent;
			System.out.println("method1 - Child로 변환 성공");
		} else {
			System.out.println("method1 - Child로 변환되지 않음");
		}
	}
	
	public static void method2(Parent parent) {
		Child child = (Child) parent;
		System.out.println("method2 - Child로 변환 성공");
	}
	
	public static void main(String[] args) {
		Parent parentA = new Child();
		method1(parentA);
		method2(parentA);
		
		Parent parentB = new Parent();
		method1(parentB);
		method2(parentB); //예외 발생
	}
}

결과:

InstanceofExample 클래스에서 method1()과 method2()를 호출할 경우, Child 객체를 매개값으로 전달하면 두 메소드 모두 예외가 발생하지 않지만 Parent 객체를 매개값으로 전달하면 ClassCastException이 발생한다.

예외가 발생하면 프로그램은 즉시 종료되기 때문에 method1()과 같이 강제 타입 변환을 하기 전에 instanceof 연산자로 변환시킬 타입의 객체인지 조사해서 잘못된 매개값으로 인해 프로그램이 종료되는 것을 막아야한다.

 

참고로 같은 클래스인지 조사하는 것은 true, false로 결과를 반환하지만 아예 잘못된 값을 입력한다면 에러가 발생한다.

728x90
반응형
LIST

'프로그래밍언어 > Java' 카테고리의 다른 글

08-1 인터페이스  (0) 2021.05.18
07-3 추상 클래스  (0) 2021.04.28
07-1 상속  (0) 2021.02.25
06-6 패키지와 접근 제한자  (0) 2021.02.23
06-5 인스턴스 멤버와 정적 멤버  (0) 2021.02.17