[JAVA] 타입 변환과 다형성(Polymorphism)

DANI·2023년 9월 25일
0

JAVA를 공부해보자

목록 보기
4/29
post-thumbnail

📕 다형성(Polymorphism) 이란?

하나의 타입에 실행결과가 다양한 여러 객체를 대입하여 다양한 기능을 구현하는 것.

메소드 재정의 + 타입변환 ➡ 다형성


📖 자동 타입 변환(promotion)

자바는 다형성을 위해 부모로의 타입변환을 허용한다.

즉, 자식 객체는 부모 객체로 대입될 수 있다.

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

비록 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된다. 그러나 예외가 있는데, 메소드가 자식 클래스에서 오버라이딩 되었다면 자식 클래스의 메소드가 대신 호출된다.




❓ 자동 타입 변환이 필요한 이유?

왜 부모 클래스 타입의 객체는 자식 클래스에서 오버라이딩된 메소드를 실행하게 되는 걸까?

예를 들어, 우리가 Tire라는 부모 클래스를 만들었다고 가정해 보자. 그런데 기술이 점점 발전하거나, 기기가 고장이 나서 타이어를 교체해야 하는 상황이 왔다고 하자. 이때, 만약 부모 타입의 클래스를 상속 받은 자식 객체에서, 더 좋은 기능을 장착한 후(오버라이딩), 타이어를 해당 타이어로 업그레이드할 수 있다면 어떨까?

우리는 이미 Tire라는 기본 클래스의 멤버와 메소드를 알고 있고(사용법을 알고 있고) 향상된 기능(오버라이딩)으로 인해 실행 결과가 우수해질 수 있다.

즉, 교체와 유지보수가 간단해진다.

부모 클래스에 있는 멤버만 접근할 수 있으나, 자식 클래스에 있는 오버라이딩된 메소드를 호출할 수 있다. 즉, 자식 클래스의 오버라이딩된(향상된) 메소드를 사용하고 싶을때

여러 자식클래스 객체를 하나의 배열로 다룰수 있다.

🔴 메인 클래스

public class PromotionExample {
	public static void main(String[] args) {
    
		Hyundai hyundai = new Hyundai(); // 자식클래스
		Tire tire = hyundai; // 자동 타입 변환
		
		if(hyundai==tire) {
			System.out.println("같은 객체 입니다.");
		} else {
			System.out.println("다른 객체 입니다.");
		}
	}
}

🔵 실행결과

같은 객체 입니다.

두 객체는 같은 참조값을 가짐




📖 필드의 다형성

🔴 Car 클래스

public class Car {
	
	// 필드 (타이어 4개)
	Tire frontLeftTire = new Tire("앞왼쪽", 6);
	Tire frontRighftTire = new Tire("앞오른쪽", 2);
	Tire backLeftTire = new Tire("뒤왼쪽",3);
	Tire backRightTire = new Tire("뒤오른쪽",4);
	
	// 메소드
    // 차가 달린다.
	int run() {
		System.out.println("[자동차가 달립니다.]");
		if(frontLeftTire.roll()==false) { // 앞왼쪽 펑크시 1 반환
			stop();
			return 1;
		}
		if(frontRighftTire.roll()==false) { // 앞오른쪽 펑크시 2 반환
			stop();
			return 2;
		}
		if(backLeftTire.roll()==false) { // 뒤왼쪽 펑크시 3 반환
			stop();
			return 3;
		}
		if(backRightTire.roll()==false) { // 뒤오른쪽 펑크시 4 반환
			stop();
			return 4;
		}
		return 0;
	}
	
	// 메소드
    // 차가 멈춘다.
	void stop() {
		System.out.println("[자동차가 멈춥니다]");	
	}
}

🔴 Tire 클래스

public class Tire {

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

🔴 Kia 클래스

public class Kia extends Tire {

	// 생성자
	Kia(String location, int maxRotation) {
		super(location, maxRotation);
	}
	
    // 메소드 오버라이딩
	@Override
	protected boolean roll() {
		++accumulatedRotation;
		if(accumulatedRotation<maxRotation) {
			System.out.println(this.location + "Tire 수명" + (maxRotation - accumulatedRotation) + "회");
			return true;
		} else {
        	// 어떤 타이어가 펑크났는지!! 이 부분만 달라짐!
			System.out.println("***" + this.location + " KiaTire 펑크 ***"); 
			return false;
		}
	}
}

🔴 Hyundai 클래스

public class Hyundai extends Tire {

	// 생성자
	Hyundai(String location, int maxRotation) {
		super(location, maxRotation);
	}
	
    // 메소드 오버라이딩
	@Override
	protected boolean roll() {
		++accumulatedRotation;
		if(accumulatedRotation<maxRotation) {
			System.out.println(this.location + "Tire 수명" + (maxRotation - accumulatedRotation) + "회");
			return true;
		} else {
        	// 어떤 타이어가 펑크 났는지!! 현대타이어가 펑크남!!
			System.out.println("***" + this.location + " HyundaiTire 펑크 ***");
			return false;
		}
	}
}

🔴 main 클래스

public class PromotionExample {
	public static void main(String[] args) {
		
		Car mycar = new Car(); // mycar 객체 생성
		
        // Car클래스의 run메소드 5번 실행(차가 움직인다)
		for(int i=1; i<=5; i++) {
			int problemLocation = mycar.run(); // run메소드는 int값을 리턴받는다
		
		switch(problemLocation) {
			case 1: // 앞왼쪽 펑크시 1 반환
				System.out.println("앞왼쪽 hyundaiTire로 교체");
				mycar.frontLeftTire = new Hyundai("앞왼쪽", 15); // 필드의 다형성
				break;
			case 2: // 앞오른쪽 펑크시 2 반환
				System.out.println("앞오른쪽 KiaTire로 교체");
				mycar.frontRighftTire = new Kia("앞오른쪽", 13); // 필드의 다형성
				break;
			case 3: // 뒤왼쪽 펑크시 3 반환
				System.out.println("뒤왼쪽 hyundaiTire로 교체");
				mycar.frontRighftTire = new Kia("뒤왼쪽", 14); // 필드의 다형성
				break;
			case 4: // 뒤오른쪽 펑크시 4 반환
				System.out.println("뒤오른쪽 KiaTire로 교체");
				mycar.frontRighftTire = new Kia("뒤오른쪽", 17); // 필드의 다형성
				break;
		}
		System.out.println("ㅡㅡㅡㅡㅡㅡㅡㅡㅡ" + i + "번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ");
		}
	}
}

mycar.frontLeftTire = new Hyundai("앞왼쪽", 15);
mycar.frontRighftTire = new Kia("앞오른쪽", 13);
mycar.frontRighftTire = new Kia("뒤왼쪽", 14);
mycar.frontRighftTire = new Kia("뒤오른쪽", 17);

필드의 다형성 : Tire(부모클래스)로 생성한 객체를 자동타입변환을 통해 Hyundai, Kia(자식 객체)를 참조할 수 있다.

🔵 실행결과

[자동차가 달립니다.]
앞왼쪽Tire 수명5회
앞오른쪽Tire 수명1회
뒤왼쪽Tire 수명2회
뒤오른쪽Tire 수명3회
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ1번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
[자동차가 달립니다.]
앞왼쪽Tire 수명4***앞오른쪽 Tire 펑크 ***
[자동차가 멈춥니다]
앞오른쪽 KiaTire로 교체
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ2번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
[자동차가 달립니다.]
앞왼쪽Tire 수명3회
앞오른쪽Tire 수명12회
뒤왼쪽Tire 수명1회
뒤오른쪽Tire 수명2회
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ3번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
[자동차가 달립니다.]
앞왼쪽Tire 수명2회
앞오른쪽Tire 수명11***뒤왼쪽 Tire 펑크 ***
[자동차가 멈춥니다]
뒤왼쪽 hyundaiTire로 교체
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ4번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
[자동차가 달립니다.]
앞왼쪽Tire 수명1회
뒤왼쪽Tire 수명13***뒤왼쪽 Tire 펑크 ***
[자동차가 멈춥니다]
뒤왼쪽 hyundaiTire로 교체
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ5번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ



📖 매개변수의 다형성

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

매개변수로 어떤 자식객체가 제공되느냐에 따라 메소드의 실행결과는 다양해질 수 있다. 자식객체가 부모의 메소드를 오버라이딩 했다면, 매개값을 넘겨받은 메소드 내부에서, 오버라이딩된 메소드를 호출함으로써 메소드의 실행결과는 다양해진다.

🔴 Driver 클래스

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

🔴 Vehicle 클래스(부모)

// 추상클래스, 인터페이스로 구현해도 무방함
public class Vehicle {
	void run() {
	}
}

🔴 Bus 클래스(자식)

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

🔴 Taxi 클래스(자식)

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

🔴 Main 클래스

public class Example {

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

🔵 실행결과

택시가 달립니다.
버스가 달립니다.



📖 강제 타입 변환(Casting)

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

부모타입이 자동타입변환으로 자식객체를 참조하게 됐을 때, 자식객체는 부모클래스의 멤버만 호출할 수 있다.

자식클래스의 멤버를 꼭 호출해야할 때 강제 타입 변환으로 변환 후 사용할 수 있다.

🔴 Parent 클래스(부모)

public class Parent {
	// 필드
	protected String field;
	
	//메소드
	protected void method1() {
		System.out.println("Parent-Method1");
	}
	
	protected void method2() {
		System.out.println("Parent-Method2");
	}
}

🔴 Child 클래스(자식)

public class Child extends Parent{
		// 필드
		protected String Childfield;
		
		//메소드
		protected void method3() {
			System.out.println("Child-Method3");
		}
}

🔴 Main 클래스

public class Example {

	public static void main(String[] args) {
		Parent parent = new Child(); // 자동 타입 변환, 부모클래스의 멤버만 호출할 수 있음.
		parent.field = "data1";
		parent.method1();
		parent.method2();
		
		System.out.println("ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ");
	
    	/* 자식클래스의 멤버를 호출할 수 없음
		parent.Childfield = "data2"; 
		parent.method3();
		*/
		
		Child child = (Child) parent; // child와 parent는 같은 객체를 참조함. 
		
		child.Childfield = "data2"; // 자식클래스의 멤버변수 호출
		child.method3(); // 자식클래스의 메소드 호출
		
		System.out.println("ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ");
		child.method1(); // 부모클래스의 메소드 호출
		child.method2(); // 부모클래스의 메소드 호출
		
	}
}

🔵 실행결과

Parent-Method1
Parent-Method2
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Child-Method3
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Parent-Method1
Parent-Method2



📖 객체 타입 확인(instanceof 연산자)

강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에 다음과 같이 부모 타입의 변수가 부모 객체를 참조할 경우 자식 타입으로 변환할 수 없다.

Parent parent = new Parent();
Child child = (Child) parent; // 강제 타입 변환할 수 없음

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

🔴 Main 클래스

public class Example {
	
	public static void method1(Parent parent) {
		if(parent instanceof Child) {
			Child child = (Child) parent;
			System.out.println("Child로 변환 성공!╰(*°▽°*)╯ - method1");
		} else {
			System.out.println("Child로 변환 실패!(┬┬﹏┬┬) - method1");
		}
	}
		
	public static void method2(Parent parent) {
		try {
		Child child = (Child) parent;
		System.out.println("Child로 변환 성공!╰(*°▽°*)╯ - method2");
		} catch(ClassCastException e) { // 예외처리
			System.out.println("ClassCastException발생!!");
		}
	}
		
	public static void main(String[] args) {
		Parent parent1 = new Child();
		method1(parent1);
		method2(parent1);
		
		Parent parent2 = new Parent();
		method1(parent2);
		method2(parent2); // ClassCastException 발생
	}
}

🔵 실행결과

Child로 변환 성공!(*°▽°*)- method1
Child로 변환 성공!(*°▽°*)- method2
Child로 변환 실패!(┬┬﹏┬┬) - method1
ClassCastException발생!!

0개의 댓글