07-2. 타입 변환과 다형성
by Hi.Claire📓 혼자 공부하는 자바 (신용권, 한빛미디어)
07. 상속
07-2. 타입 변환과 다형성
다형성
사용 방법은 동일하지만 다양한 객체를 사용해서 다양한 실행 결과가 나오도록 하는 성질
다형성을 구현하려면 메소드 재정의와 타입 변환이 필요하다.
07-2-1. 자동 타입 변환
자동 타입 변환(Promotion)
프로그램 실행 도중에 자동으로 타입 변환이 일어나는 것
클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생하며, 자식 타입은 부모 타입으로 자동 타입 변환이 가능하다.
자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다는 것이다.
예를 들어, 고양(Cat)이가 동물(Animal)의 특징과 기능을 상속받았다면 '고양이는 동물이다'가 성립한다.
또한 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이면 자동 타입 변환이 일어날 수 있다.
예를 들어, 고양(Cat)이가 동물(Animal)의 특징과 기능을 상속받고, 동물(Animal)이 생물(Life)의 특징과 기능을 상속받았다면 '고양이는 생물이다' 역시 성립한다.
예시1. 부모 클래스
public class Animal { ... }
예시2. 자식 클래스
public class Cat extends Animal { ... }
부모는 자식을 낳을 수 있다.
Animal animal = new Cat();
단, 자식은 부모를 낳을 수 없다.
// Cat cat = new Animal();
부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
그러나 메소드가 자식 클래스에서 재정의되었다면 자식 클래스의 재정의된 메소드가 대신 호출된다.
-> 다형성과 관련된 매우 중요한 성질!
예시1. 부모 클래스
public class Parent {
public void method1() {
System.out.println("Parent-method1()");
}
public void method2() {
System.out.println("Parent-method2()");
}
}
예시2. 자식 클래스
public class Child extends Parent {
@Override
public void method2() {
System.out.println("Child-method2()");
}
public void method3() {
System.out.println("Child-method3()");
}
}
예시3. 자동 타입 변환 후의 멤버 접근
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()
07-2-2. 필드의 다형성
필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가 달라질 수 있다.
이것이 필드의 다형성이다.
자동차 클래스에 포함된 타이어 클래스를 생각해보자.
자동차 클래스를 처음 설계할 때 사용한 타이어 객체는 언제든지 성능이 좋은 다른 타이어 객체로 교체될 수 있어야 한다.
새로 교체되는 타이어 객체는 기존 타이어와 사용 방법은 동일하지만 실행 결과는 더 우수하게 나와야 한다.
이것을 프로그램으로 구현하기 위해서 상속과 재정의, 타입 변환을 이용한다.
부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있기 때문에 사용 방법이 동일하다.
자식 클래스는 부모의 메소드를 재정의해서 메소드의 실행 내용을 변경함으로써 더 우수한 실행 결과가 나오게 할 수도 있다.
그리고 자식 타입을 부모 타입으로 변환할 수 있다.
이 세가지가 다형성을 구현할 수 있는 기술적 조건이 된다.
예시1. 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;
if(accumulatedRotation < maxRotation) {
System.out.println(location + " Tire 수명 : " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + " Tire 펑크 ***");
return false;
}
}
}
예시2. Car 클래스
public class Car {
//필드
Tire frontLeftTire = new Tire("앞왼쪽", 6);
Tire frontRightTire = new Tire("앞오른쪽", 2);
Tire backLeftTire = new Tire("뒤왼쪽", 3);
Tire backRightTire = new Tire("뒤오른쪽", 4);
//생성자
//메소드
int run() {
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("자동차가 멈춥니다.");
}
}
예시3. Tire의 자식 클래스1
public class HankookTire extends Tire {
//필드
//생성자
public HankookTire(String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
@Override
public boolean roll()
++accumulatedRotation;
if(accumulatedRotation < maxRotation) {
System.out.println(location + " HankookTire 수명 : " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + "HankookTire 펑크 ***");
return false;
}
}
}
예시4. Tire의 자식 클래스2
public class KumhoTire extends Tire {
//필드
//생성자
public KumhoTire(String location, int maxRotation) {
super(location, maxRotation);
}
//메소드
@Override
public boolean roll()
++accumulatedRotation;
if(accumulatedRotation < maxRotation) {
System.out.println(location + " KumhoTire 수명 : " + (maxRotation - accumulatedRotation) + "회");
return true;
} else {
System.out.println("*** " + location + "KumhoTire 펑크 ***");
return false;
}
}
}
예시5. 필드의 다형성
public class CarExample {
public static void main(String[] args) {
Car car = new Car();
for(int i=0; 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;
default:
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회
-------------------------
위의 예시를 보면 Car 클래스는 4개의 Tire 필드를 가지고 있다.
처음 Car 객체를 생성할 때 각각의 Tire 필드에 각각 하나씩 Tire 객체가 들어가게 된다.
그런데 Car 객체의 run() 메소드가 실행되며 frontRightTire와 backLeftTire를 각각 KumhoTire와 HankookTire로 교체하게 되었다.
이는 자식 타입은 부모 타입으로 자동 타입 변환되며, Car 객체에서 부모 클래스(Tire 클래스)에 정의된 필드와 메소드만 사용하기 때문에 아무 문제없이 가능하다.
이때 run() 메소드는 각 Tire 객체의 roll() 메소드를 호출한다.
자식 타입인 KumhoTire와 HankookTire는 부모 타입인 Tire 클래스의 roll() 메소드를 재정의했기 때문에 각각의 재정의된 메소드가 호출된다.
이와 같이 자동 타입 변환을 이용해서 부모 타입인 Tire 필드값을 자식 타입인 KumhoTire와 HankookTire로 교체함으로써 Car의 run() 메소드를 수정하지 않아도 다양한 roll() 메소드의 실행 결과를 얻게 된다.
이것이 바로 필드의 다형성이다.
07-2-3. 매개 변수의 다형성
메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수도 있다.
이때 매개 변수의 타입이 클래스일 경우, 해당 클래스의 객체뿐만 아니라 자식 객체까지도 매개값으로 사용할 수 있다.
자식 객체가 부모의 메소드를 재정의했다면, 메소드 내부에서 재정의된 메소드를 호출함으로써 메소드의 실행 결과가 다양해진다.
예시1. 부모 클래스
public class Vehicle {
public void run() {
System.out.println("차량이 달립니다.");
}
}
예시2. Vehicle을 이용하는 클래스
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
예시3. Vehicle의 자식 클래스 - Bus
public class Bus extends Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.);
}
}
예시4. Vehicle의 자식 클래스 - Taxi
public class Taxi extends Vehicle {
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
예시5. 실행 클래스
public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus);
driver.drive(taxi);
]
}
버스가 달립니다.
택시가 달립니다.
위와 같이 매개값의 자동 타입 변환과 메소드 재정의를 이용해서 매개 변수의 다형성을 구현할 수 있다.
07-2-4. 강제 타입 변환
강제 타입 변환(Casting)
부모 타입을 자식 타입으로 변환하는 것
단, 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때에만 강제 타입 변환을 사용할 수 있다.
처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환할 수 없다.
자식타입 변수 = (자식타입) 부모타입;
자식 타입이 부모 타입으로 자동 타입 변환하면 부모에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따른다.
만약 자식에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환한 다음 자식의 필드와 메소드를 사용하면 된다.
예시1. 부모 클래스
public class Parent {
public String field1;
public void method1() {
System.out.println("Parent-method1()");
}
public void method2() {
System.out.println("Parent-method2()");
}
}
예시2. 자식 클래스
public class Child extends Parent {
public String field2;
public void method3() {
System.out.println("Child-method3()");
}
}
예시3. 강제 타입 변환
public class ChildExample {
public static void main(String[] args) {
Parent parent = new Child(); // 자동 타입 변환
parent.field1 = "Data1";
parent.method1();
parent.method2();
//parent.field2 = "Data2";
//parent.method3();
Child child = (Child) parent; // 강제 타입 변환
child.field2 = "Data3";
child.method3();
}
}
Parent-method1()
Parent-method2()
Child-method3()
07-2-5. 객체 타입 확인
instanceof 연산자
어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해 사용하는 연산자
boolean result = 좌항(객체) instanceof 우항(타입);
좌항의 객체가 우항의 인스턴스이면 true를, 그렇지 않으면 false를 리턴한다.
instanceof는 주로 매개값의 타입을 조사할 때 사용된다.
메소드 내에서 강제 타입 변환이 필요할 경우 반드시 매개값이 어떤 객체인지 instanceof로 확인하고 안전하게 강제 타입 변환을 해야 한다.
만약 타입을 확인하지 않고 강제 타입 변환을 시도하면 ClassCastException이 발생할 수 있다.
예시1. 부모 클래스
public class Parent { ... }
예시2. 자식 클래스
public class Child extends Parent { ... }
예시3. 객체 타입 확인
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);
}
}
method1 - Child로 변환 성공
method2 - Child로 변환 성공
method1 - Child로 변환 실패
Exception in thread "main" java.lang.ClassCastException
예외가 발생하면 프로그램은 즉시 종료되기 때문에 method1()과 같이 강제 타입 변환을 하기 전에 instanceof 연산자로 변환시킬 타입의 객체인지 조사해서 잘못된 매개값으로 인해 프로그램이 종료되지 않도록 해야 한다.
'☕️ Java > 혼자 공부하는 자바' 카테고리의 다른 글
08-1. 인터페이스 (0) | 2023.09.24 |
---|---|
07-3. 추상 클래스 (0) | 2023.09.24 |
07-1. 상속 (0) | 2023.09.18 |
06-6. 패키지와 접근 제한자 (0) | 2023.09.17 |
06-5. 인스턴스 멤버와 정적 멤버 (0) | 2023.09.13 |
블로그의 정보
Claire's Study Note
Hi.Claire