[Java]타입 변환과 다형성

Devlog·2024년 3월 15일

Java

목록 보기
18/41

✔️ 자동 타입 변환

- 타입 변환
: 타입을 다른 타입으로 변환하는 행위
: 클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생
: 자식은 부모 타입으로 자동 타입 변환이 가능함

- 자동 타입 변환

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

: 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것
: 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있음

ex)
고양이가 동물의 특징과 기능을 상속받았다면
'고양이는 동물이다.'가 성립함

Animal 클래스(부모, 상위) ← Cat 클래스(자식, 하위)
class Animal { ··· } ← (상속) class Cat extends Animal { ··· }


: Cat 클래스로부터 Cat 객체를 생성하고 
  이것을 Animal 변수에 대입하면 자동 타입 변환이 일어남

Cat cat = new Cat();
Animal animal = cat;

→ Animal animal = new Cat(); 도 가능함

: cat과 animal 변수는 타입만 다를 뿐, 동일한 Cat 객체를 참조함

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

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;
	}
}

: 부모타입으로 자동 타입 변환된 이후에는
  부모 클래스에 선언된 필드와 메소드만 접근이 가능
: 변수는 자식 객체를 참조하지만
  변수로 접근 가능한 멤버부모 클래스 멤버로만 한정
  예외는 메소드가 자식 클래스에서 재정의됐다면
  자식 클래스의 메소드가 대신 호출
  → 다형성과 관련있는 성질

: Child 객체는 method3() 메소드를 가지고 있지만,
  Parent 타입으로 변환된 이후에는 method3()을 호출할 수 없음
  그러나, method2() 메소드는 부모와 자식 모두에게 있음
  이렇게 재정의된 메소드는 타입 변환 이후에도 자식 메소드가 호출됨

👩‍💻 자동 타입 변환 후의 멤버 접근
// 부모 클래스
class Parent {
	
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}

// 상속받은 자식 클래스
class Child extends Parent {
	
	// method2() 재정의
	@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(); ← 호출 불가능
	}
}

💻 결과
Parent-method1()
Child-method2()

✔️ 필드의 다형성

: 필드의 타입을 부모 타입으로 선언하면
  다양한 자식 객체들이 저장될 수 있기 때문에
  필드 사용 결과가 달라질 수 있음

ex)
자동차를 구성하는 부품은 언제든지 교체할 수 있음
부품은 고장이 날 수 도 있고, 성능이 더 좋은 부품으로 교체되기도 함

객체 지향 프로그래밍도 마찬가지로
프로그램은 수많은 객체들이 서로 연결되고 각자의 역할을 하게 됨
이 객체들은 다른 객체로 교체될 수 있어야함

→ 상속과 재정의, 타입 변환을 이용하여 구현

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

👩‍💻 Tire 클래스
public class Tire {
	//필드
	public int maxRotation;				// 최대 회전수(타이어 수명)
	public int accumulatedRotation;		// 누적 회전수
	public String location;				// 타이어의 위치
	
	//생성자
	public Tire(String location, int maxRotation) {
		// (초기화)필드 - 타이어의 위치 			
		this.location = location; 	
		// (초기화)필드 - 최대 회전수(타이어 수명)	
		this.maxRotation = maxRotation;		
	}
	
	//메소드
	public boolean roll() {
		++accumulatedRotation; // 누적 회전수 1증가
		if(accumulatedRotation < maxRotation) {
			// 정상 회전(누적<최대)일 경우 실행
			System.out.println(location + " Tire 수명: " +
				(maxRotation - accumulatedRotation) + "회");
			return true;
		}else {
			// 펑크(누적=최대)일 경우 실행
			System.out.println("*** " + location + " Tire 펑크 ***");
			return false;
		}
	}
}

👩‍💻 Tire를 부품으로 가지는 클래스
public class Car {
	
	//필드
	//최대 회전수: 6회전 시 타이어에 펑크가 남
	Tire frontLeftTire = new Tire("앞왼쪽", 6);
	Tire frontRightTire = new Tire("앞오른쪽", 2);
	Tire backLeftTire = new Tire("뒤왼쪽", 3);
	Tire backRightTire = new Tire("뒤오른쪽", 4);

	//생성자
	
	//메소드
	int run() {
		/* 모든 타이어를 1회 회전시키기 위해
		 * 각 Tire 객체의 roll() 메소드를 호출
		 * false를 리턴하는 roll()이 있을 경우
		 * stop() 메소드를 호출하고
		 * 해당 타이어 번호를 리턴
		 */
		System.out.println("[자동차가 달립니다.]");
		if(frontLeftTire.roll()==false) {
			stop(); return 1;
		}
		if(frontRightTire.roll()==false) {
			stop(); return 2;
		}
		if(backLeftTire.roll()==false) {
			stop(); return 3;
		}
		if(backRightTire.roll()==false) {
			stop(); return 4;
		}
		
		return 0;
	}
	
	void stop() {
		System.out.println("[자동차가 멈춥니다.]");
	}
}

👩‍💻 Tire의 자식 클래스
public class HankookTire extends Tire {
	//필드
	//생성자
	public HankookTire(String location, int maxRotation) {
		super(location, maxRotation);
	}
	
	//메소드
	@Override
	public boolean roll() {
		++accumulatedRotation; // 누적 회전수 1증가
		if(accumulatedRotation < maxRotation) {
			System.out.println(location + " HankookTire 수명: " +
				(maxRotation - accumulatedRotation) + "회");
			return true;
		}else {
			System.out.println("*** " + location + " HankookTire 펑크 ***");
			return false;
		}
	}
}

👩‍💻 Tire의 자식 클래스
public class KumhoTire extends Tire{
	
	//필드
	//생성자
	public KumhoTire(String location, int maxRotation) {
		super(location, maxRotation);
	}
	
	//메소드
	@Override
	public boolean roll() {
		++accumulatedRotation; // 누적 회전수 1증가
		if(accumulatedRotation < maxRotation) {
			System.out.println(location + " KumhoTire 수명: " +
				(maxRotation - accumulatedRotation) + "회");
			return true;
		}else {
			System.out.println("*** " + location + " KumhoTire 펑크 ***");
			return false;
		}
	}
}

👩‍💻 CarExample 클래스
public class CarExample {
	public static void main(String[] args) {
		
		//Car객체 생성
		Car car = new Car();
		
		//Car객체의 run()메소드 5번 반복실행
		for(int i=1; i<=5; i++) {
			int problemLocation = car.run();
			
			switch(problemLocation) {
				//앞왼쪽 타이어가 펑크 났을 때 HankookTire로 교체
				case 1:
					System.out.println("앞왼쪽 HankookTire로 교체");
					car.frontLeftTire = new HankookTire("앞왼쪽", 15);
					break;
				//앞오른쪽 타이어가 펑크 났을 때 KumhoTire로 교체
				case 2:
					System.out.println("앞오른쪽 KumhoTire로 교체");
					car.frontRightTire = new KumhoTire("앞오른쪽", 13);
					break;
				//뒤왼쪽 타이어가 펑크 났을 때 HankookTire로 교체
				case 3:
					System.out.println("뒤왼쪽 HankookTire로 교체");
					car.backLeftTire = new HankookTire("뒤왼쪽", 14);
					break;
				//뒤오른쪽 타이어가 펑크 났을 때 KumhoTire로 교체
				case 4:
					System.out.println("뒤오른쪽 KumhoTire로 교체");
					car.backRightTire = new KumhoTire("뒤오른쪽", 13);
					break;
			}
			
			System.out.println("----------------------------------");
		}
	}
}

💻 결과
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 5회
앞오른쪽 Tire 수명: 1회
뒤왼쪽 Tire 수명: 2회
뒤오른쪽 Tire 수명: 3----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 4*** 앞오른쪽 Tire 펑크 ***
[자동차가 멈춥니다.]
앞오른쪽 KumhoTire로 교체
----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 3회
앞오른쪽 KumhoTire 수명: 12회
뒤왼쪽 Tire 수명: 1회
뒤오른쪽 Tire 수명: 2----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 2회
앞오른쪽 KumhoTire 수명: 11*** 뒤왼쪽 Tire 펑크 ***
[자동차가 멈춥니다.]
뒤왼쪽 HankookTire로 교체
----------------------------------
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 1회
앞오른쪽 KumhoTire 수명: 10회
뒤왼쪽 HankookTire 수명: 13회
뒤오른쪽 Tire 수명: 1----------------------------------

✔️ 매개 변수의 다형성

: 자동 타입 변환은 주로 메소드를 호출할 때 많이 발생
: 메소드를 호출할 때는 매개값을 다양화하기 위해
  매개 변수에 자식 객체를 지정 할 수도 있음

ex) 
Driver 클래스에는 drive() 메소드가 정의되어 있는데
Vehicle타입의 매개 변수가 선언되어 있음
class Driver {
	void drive(Vehicle vehicle){
		vehicle.run();
	}
}

- ★★ drive()메소드를 정상적으로 호출 시 ★★,
  Driver driver = new Driver();
  Vehicle vehicle = new Vehicle();
  driver.drive(vehicle);

→ 만약 Vehicle의 자식 클래스인 Bus 객체를 
  drive()메소드의 매개값으로 넘겨준다면
  
   Driver driver = new Driver();
   Vehicle vehicle = new Vehicle();
   driver.drive(bus);

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

> Vehicle vehicle = bus;

- 매개 변수의 타입이 클래스일 경우,
 해당 클래스이 객체뿐만 아니라
 자식 객체까지도 매개값으로 사용할 수 있음

→ 즉, 매개값으로 어떤 자식 객체가 제공되느냐에 따라
  메소드의 실행결과는 다양해질 수 있음
→ 자식 객체가 부모의 메소드를 재정의했다면
  메소드 내부에서 재정의된 메소드를 호출함으로써
  메소드의 실행결과는 다양해짐

👩‍💻 부모 클래스
class Vehicle {
	public void run(){
		System.out.println("차량이 달립니다.");
	}
}

//Vehicle을 이용하는 클래스
class Driver{
	public void drive(Vehicle vehicle) {
		vehicle.run();
	}
}

👩‍💻 자식클래스
class Bus extends Vehicle {
	
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}

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

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

💻 결과
버스가 달립니다.
택시가 달립니다.

✔️ 강제 타입 변환

부모타입 변수 = new 자식();
자식타입 변수 = (자식타입) 부모타입;

: 부모 타입을 자식 타입으로 변환하는 것
: 자식 타입이 부모 타입으로 자동 타입 변환한 후
  다시 자식 타입으로 변환할 때 강제 타입 변환을 사용가능 함

ex)
Child 객체가 Parent 타입으로 자동 변환된 상태에서
원래 Child로 강제 변환할 수 있음

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

👩‍💻 강제 타입 변환

//부모 클래스
class Parent2{
	public String field1;
	
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}

//자식 클래스
class Child2 extends Parent2{
	public String field2;
	
	public void method3() {
		System.out.println("Child-method3()");
	}
}

// 실행 클래스
public class ChildExample2 {
	public static void main(String[] args) {
		Parent2 parent = new Child2(); //자동 타입 변환	
		
		parent.field1 = "data1";
		parent.method1();
		parent.method2();
		/* 불가능
		parent.field2 = “yyy”; 
		parent.method3();
		*/
		
		Child2 child = (Child2) parent; //강제 타입 변환
		child.field2 = "yyy"; // 가능
		child.method3(); //가능

	}
}

💻 결과
Parent-method1()
Parent-method2()
Child-method3()

✔️객체 타입 확인

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

→ 좌항의 객체가 우항의 인스턴스이면,
  즉, 우항의 타입으로 객체가 생성되었다면 true를 리턴
  그렇지않으면 false로 리턴

→ 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하는 방법
: 어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해
  instanceof 연산자를 사용

: instanceof 연산자는 주로 매개값의 타입을 조사할 때 사용됨
: 메소드 내에서 강제 타입 변환이 필요할 경우 반드시
  매개값이 어떤 객체인지 instanceof 연산자로 확인하고
  안전하게 강제 타입 변환을 해야함

ex)
public void method(Parent parent){
	// Parent 매개 변수가 참조하는
	// 객체가 Child인지 조사
	if(parent instanceof Child) {
		Child child = (Child) parent;
	}
}

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

👩‍💻 객체 타입 확인
//부모 클래스
class Parent3{
	
}

//자식 클래스
class Child3 extends Parent3{
	
}

public class InstanceofExample {
	public static void method1(Parent3 parent) {
		//Child 타입으로 변환이 가능한지 확인
		if(parent instanceof Child3) { 
			Child3 child = (Child3) parent;
			System.out.println("method1 - Child로 변환 성공");
		} else {
			System.out.println("method1 - Child로 변환되지 않음");
		}
	}
	
	public static void method2(Parent3 parent) {
		// classcastException이 발생할 가능성이 있음
		Child3 child = (Child3) parent;
		System.out.println("method2 - Child로 변환 성공");
	}
	
	public static void main(String[] args) {
		Parent3 parentA = new Child3();
		
		//Child 객체를 매개값으로 전달
		method1(parentA);
		method2(parentA);
		
		Parent3 parentB = new Parent3();
		//Parent 객체를 매개값으로 전달
		method1(parentB);
		method2(parentB);
	}
}

💻 결과
method1 - Child로 변환 성공
method2 - Child로 변환 성공
method1 - Child로 변환되지 않음
오류 문구 ··· // 무조건 변환하려고 했기 때문

0개의 댓글