코딩 이야기

JAVA 타입변환과 다형성 본문

JAVA

JAVA 타입변환과 다형성

별메아리 2023. 2. 13. 16:21
728x90

타입변환과 다형성

타입변환을 이용하면 객체 지향 프로그래밍의 중요한 특징인 다형성을 구현할 수 있습니다.

다형성은 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 프로그램입니다.

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

 

자동 타입 변환

기본타입과 같이 클래스도 타입변환이 있는데 클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생합니다. 자식은 부모타입으로 자동 타입 변환이 가능합니다.

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

 

부모타입 변수 = 자식타입;

       ↑                        ↓

                   ←

         자동 타입 변환

 

예를 들어  Cat 클래스로부터 Cat 객체를 생성하고 이것을 Animal 변수에 대입하면 자동 타입 변환이 일어납니다.

Cat cat = new Cat();           // Animal animal = new Cat(); 도가능
Animal animal = cat;

car과 animal은 변수만 다를 뿐 동일안 Cat 객체를 참조합니다.

cat == animal

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

 

자동 타입 변환

 

package ch07;

class A{}

class B extends A {}
class C extends A {}

class D extends B {}
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;   상속관계있지 않기 때문에 컴파일에러
	}
}

부모 타입으로 자동 타입 변환 이후에는 부모 클래스의 선언된 필드와 메소드만 접근이 가능합니다. 비록 변수는 자식객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정됩니다. 그러나 예외가 있는데 메소드가 자식 클래스에서 재정의 되었다면 자식 클래스의 메소드가 대신 호출됩니다. 이것은 다형성과 크게상관이 있습니다.

 

자동 타입 변환 후의 멤버 접근

package ch07;

public class Parent {
	public void method1() {
		System.out.println("parent-method()");
	}
	
	public void method2() {
		System.out.println("Parent=method2()");
	}
}

자동 타입 변환 후의 멤버 접근

package ch07;

public class Child extends Parent{
	@Override
	public void method2() {
		System.out.println("Child-method2()");  // 재정의
	}
	
	public void meethod3() {
		System.out.println("child-method3()");
	}
}

자동 타입 변환 후의 멤버 접근

package ch07;

public class ChildExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Child child = new Child();
		
		Parent parent = child;  // 자동 타입 변환
		parent.method1();
		parent.method2();  // 재정의된 메소드가 호출됨
		//parent.method3();  호출불가능
	}
}

필드의 다형성

부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으니 사용 방법이 동일할 것입니다. 자식 클래스는 부모의 메소드를 재정의해서 메소드의 실행 내용을 변경함으로써 더 우수한 실행결과가 나오게 할 수도 있습니다. 그리고 자식 타입을 부모타입으로 변환할 수 있습니다. 이 세가지 다형성을 구현할 수 있는 기술적 조건이 됩니다.

 

필드의 다형성을 코드로 살펴봅시다.

class CAr {
// 필드
Tire frontLeftTire = new Tire();
Tire frontRightTire = new Tire();
Tire backLeftTire = new Tire();
Tire backRightTire = new Tire();
// 메소드
void run() {...}

Car 클래스는 4개의 Tire 필드를 가지고 있습니다. Car 클래스로부터 Car 객체를 생성하면 4개의 Tire 필드에 각각 하나씩 Tire 객체가 들어가게 됩니다. 그런데 frontRightTire 와 backLeftTire를 HonkookTire와 KumhoTire로 교체할 이유가 생겼습니다. 이러한 경우 다음과 같은 코드로 교체할수 있습니다.

Car my Car = new Car();
myCar.frontRightTire = new HankookTire();
myCar.backLeftTire = new KumhoTire();
myCar.run();

Tire 클래스 타입인 frontRightTire 와 backLeftTire는 원래 Trie 객체가 저장되어야 하지만 Tire의 자식 객체가 저장되어도 문제가 없습니다. 왜냐하면 자식 타입은 부모타입으로 자동 타입 변환이 되기 때문에 frontRightTire 와 backLeftTire에 Tire 자식 객체가 저장되어도 Car 객체는 Tire 클래스에 선언된 필드와 메소드만 사용하므로 문제가 되지않습니다.

 HonkookTire와 KumhoTire는 부모인 Tire의 필드와 메소드를 갖고 있기 때문입니다.

 

Car 객체에 run() 메소드가 있고, run()메소드는 각 Trie 객체의 roll() 메소드를 다음과 같이 호출한다고 가정해보겠습니다.

void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTrie.roll();
}

frontRightTire 와 backLeftTire를 교체하기 전에는 Trie 객체의 roll() 메소드가 호출되지만 HonkookTire와 KumhoTire로 교체되면  HonkookTire와 KumhoTire가 roll() 메소드를 재정의하고 있으므로 교체 이후에는 HonkookTire와 KumhoTire의 roll() 메소드가 호출되어 실행 결과가 달라집니다. 

 

이와 가이 자동 타입 변환을 이용해서 Tire 필드값을 교체함으로써 Car의 run() 메소드를 수정하지 않아도 다양한 roll()의 메소드 실행결과를 얻게됩니다. 이게 바로 필드의 다형성입니다.

 

Tire 클래스

package ch07;

public class Tire {
	//필드
	public int maxRotation; // 최대 회전수 (타이어 수명)
	public int accumulateRotation; // 누적 회전수
	public String location; // 타이어의 위치
	
	//생성자
	public Tire(String location, int maxRotation) {
		this.location = location;
		this.maxRotation = maxRotation;
	}
	
	//메소드
	public boolean roll() {
		++accumulateRotation;  // 누적 회전수 1증가
		if(accumulateRotation<maxRotation) {
			System.out.println(location + " Tire 수명:"+ (maxRotation-accumulateRotation)+"회");
			return true;
		} else {
			System.out.println("***"+location+"Tire 펑크 ***");
			return false;
		}
	}
}

Tire를 부품으로 가지는 클래스

package ch07;

public class Car2 {
	//필드
	Tire frontLeftTire = new Tire("앞왼쪽",6);         
	Tire frontRightTire = new Tire("앞오른쪽",2);
	Tire backLeftTire = new Tire("뒤왼쪽",3);
	Tire backRightTire = new Tire("뒤 오른쪽",4);
	// 자동차는 4개의 타이어를 가짐
	
	//생성자
	//메소드
	int run() {
		System.out.println("[자동차가 달립니다.]");
		if(frontLeftTire.roll()==false) {stop();return 1;}
		if(frontLeftTire.roll()==false) {stop();return 2;}
		if(frontLeftTire.roll()==false) {stop();return 3;}
		if(frontLeftTire.roll()==false) {stop();return 4;}
		return 0;
		//모든 타이어를 1회 회전 시키기 위해 각 Tire객체의 roll() 메소드를 호출 하고 false를 리턴하는 roll()이 있을경우 stop() 메소드를 호출하고 해당 타이어 번호를 리턴
	}
	
	void stop() {
		System.out.println("[자동차가 멈춥니다.]"); //펑크 났을때 실행
	}

}

Tire의 자식 클래스

package ch07;

public class HankookTire extends Tire {
	//필드
	//생성자
	public HankookTire(String location, int maxRotation) {
		super(location, maxRotation);
	}
	
	//메소드
	@Override
	public boolean roll() {
		++accumulateRotation;
		if(accumulateRotation<maxRotation) {
			System.out.println(location + "HankTire 수명 : "+ (maxRotation-accumulateRotation)+"회");
			return true;
		} else {
			System.out.println("***"+ location +" HankookTire 펑크 ***");
			return false;
		}
	}
}

Tire의 자식 클래스

package ch07;

public class KumhoTire extends Tire {
	//필드
	//생성자
	public KumhoTire(String location, int maxRotation) {
		super(location, maxRotation);
	}
	
	//메소드
	@Override
	public boolean roll() {
		++accumulateRotation;
		if(accumulateRotation<maxRotation) {
			System.out.println(location + "KumhoTire 수명 : "+ (maxRotation-accumulateRotation)+"회");
			return true;
		} else {
			System.out.println("***"+ location +" KumhoTire 펑크 ***");
			return false;
		}
	}
}

실행 클래스 (CarExample)

package ch07;

public class CarExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Car2 car = new Car2();
		
		for(int i=1; i<=5; i++) {
			int problemLocation = car.run();
			
			switch(problemLocation) {
			 case 1:
				 System.out.println("앞왼쪽 HankookTire로 교체");
				 car.frontLeftTire = new HankookTire("앞왼쪽", 15);
				 break;
			 case 2:
				 System.out.println("앞오른쪽 KumhoTire로 교체");
				 car.frontRightTire = new KumhoTire("앞오른쪽", 13);
				 break;
			 case 3:
				 System.out.println("뒤왼쪽 HankookTire로 교체");
				 car.backLeftTire = new HankookTire("뒤왼쪽", 14);
				 break;
			 case 4:
				 System.out.println("뒤 오른쪽 KumhoTire로 교체");
				 car.backRightTire = new KumhoTire("뒤오른쪽", 17);
				 break;
			}
			System.out.println("-------------------------------------");
		}
	}

}

매개 변수의 다형성

자통 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생합니다.

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

 

아래 예제를 살펴보면 Driver 클래스에는 drive() 메소드가 정의되어 있는데 Vehicle 타입의 매개 변수가 선언되어 있습니다.

class Driver {
 void drive(Vehice vehcle) {
  vehcle.run();
  }
 }

drive() 메소드를 정상적으로 호출한다면 다음과 같습니다.

Driver driver =new Driver();
vehicle vehcle = new Vehucle();
driver.drive(vehicle);

만약 Vehicle의 자식클래스인 Bus 객체를 drive() 메소드의 매개값으로 넘겨준다면?

Driver driver = new Dirver();
Bus bus = new Bus();
driver.drive( bus )

drive()메소드는 Vehicle 타입을 매개 변수로 선언했지만, Vehicle 을 상속받는 Bus 객체가 매개값으로 사용되면 자동 타입변환이 발생합니다.

Vehicle vehicle = bus; (자동타입변환)

 

매개 변수의 타입이 클래스일 경우, 해당 클래스의 객체뿐만 아니라 자식 객체 까지도 매개값으로 사용할 수 있다는 것입니다. 즉, 매개값으로 어떤 자식 객체가 제공되느냐에 따라 메소드의 실행결관느 다양해질 수 있습니다. 자식객체가 부모의 메소드를 재정의했따면 메소드 내부에서 재정의된 메소드를 호출함으로써 메소드의 실행 결과는 다양해집니다.

void drive(Vehice vehicle) // Vehicle vehicle는 자식객체
 vehicle run();   // 자식객체가 재정의한 run() 메소드 실행
}

부모클래스

package ch07;

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

Vehicle을 이용하는 클래스

package ch07;

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

자식클래스

package ch07;

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

자식클래스

package ch07;

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

실행클래스

package ch07;

public class DriverExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Driver driver= new Driver();
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		
		driver.drive(bus);   // 자동 타입 변환 : vehicle vehicle = bus;
		driver.drive(taxi);  // 자동 타입 변환 : vehicle vehicle = taxi;
		
	}
}

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

 

강제 타입 변환

강제 타입 변환은 부모 타입을 자식 타입으로 변환하는 것을 말합니다. 그렇다고 해서 모든 부모 타입을 자식 타입으로 강제 변환할 수 있는 것은 아닙니다. 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있습니다.

 

자식타입 변수 = (자식타입) 부모타입;
                  //부모 타입을 자식 타입으로 변환

 

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

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

자식타입이 부모타입으로 자동타입변환하면 부모에 선언된 필드와 메소드만 사용가능하다는 제약사항이 따르는데 자식에 선언된 필드와 메소드를 꼭 사용해야한다면 강제 타입 변환으로 자식타입으로 변환후 자식의 필드와 메소드를 사용하면 됩니다.

 

class Parent {
String field1;
void method1() {...}
void method2() {...}
}

                                                                                       ↑ 상속

class Child extends Parent {
String field2;
void method3() {...}
}

실행

class ChildExample {
	public static void main(String[] args) {
    	parent.parent = new Child();
        parent.field1 = "xxx";
        parent.method1();
        parent.method2();
        parent.field2 = "yyy"; // 불가능
        parent.method3();      // 불가능
        
        Child Child = (Child) parent;
        child.field2 = "yyy" // 가능
        child.method3();     // 가능
728x90

'JAVA' 카테고리의 다른 글

JAVA 추상 클래스  (0) 2023.02.15
JAVA 객체타입확인  (0) 2023.02.15
JAVA 메소드 재정의  (0) 2023.02.13
JAVA 상속  (0) 2023.02.13
JAVA 접근 제한자  (0) 2023.02.10
Comments