하나의 타입에 실행결과가 다양한 여러 객체를 대입하여 다양한 기능을 구현하는 것.
메소드 재정의 + 타입변환 ➡
다형성
자바는 다형성을 위해 부모로의 타입변환을 허용한다.
즉, 자식 객체는 부모 객체로 대입될 수 있다.
부모 타입으로 자동 타입 변환된 이후에는
부모 클래스에 선언된 필드와 메소드만 접근이 가능
하다.
비록 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된다. 그러나 예외가 있는데, 메소드가 자식 클래스에서 오버라이딩 되었다면 자식 클래스의 메소드가 대신 호출
된다.
왜 부모 클래스 타입의 객체는 자식 클래스에서 오버라이딩된 메소드를 실행하게 되는 걸까?
예를 들어, 우리가 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("다른 객체 입니다.");
}
}
}
같은 객체 입니다.
두 객체는 같은 참조값을 가짐
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("[자동차가 멈춥니다]");
}
}
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;
}
}
}
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;
}
}
}
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;
}
}
}
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번째이동ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
메소드의 매개변수 타입이 클래스일 경우, 해당 클래스의 객체뿐만 아니라 자식객체까지도 매개값으로 사용할 수 있다.
매개변수로 어떤 자식객체가 제공되느냐에 따라 메소드의 실행결과는 다양해질 수 있다. 자식객체가 부모의 메소드를 오버라이딩 했다면, 매개값을 넘겨받은 메소드 내부에서, 오버라이딩된 메소드를 호출함으로써 메소드의 실행결과는 다양해진다.
public class Driver {
void driver(Vehicle vehicle) {
vehicle.run();
}
}
// 추상클래스, 인터페이스로 구현해도 무방함
public class Vehicle {
void run() {
}
}
public class Bus extends Vehicle{
void run() {
System.out.println("버스가 달립니다.");
}
}
public class Taxi extends Vehicle{
void run() {
System.out.println("택시가 달립니다.");
}
}
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 자동타입변환
}
}
택시가 달립니다.
버스가 달립니다.
부모타입을 자식타입으로 변환하는 것을 말하며, 모든 부모 타입을 자식 타입으로 강제 변환할 수 있는 것은 아니고 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 사용할 수 있다.
부모타입이 자동타입변환
으로 자식객체를 참조하게 됐을 때, 자식객체는 부모클래스의 멤버만 호출할 수 있다.
자식클래스의 멤버를 꼭 호출해야할 때 강제 타입 변환으로 변환 후 사용할 수 있다.
public class Parent {
// 필드
protected String field;
//메소드
protected void method1() {
System.out.println("Parent-Method1");
}
protected void method2() {
System.out.println("Parent-Method2");
}
}
public class Child extends Parent{
// 필드
protected String Childfield;
//메소드
protected void method3() {
System.out.println("Child-Method3");
}
}
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
강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에 다음과 같이 부모 타입의 변수가 부모 객체를 참조할 경우 자식 타입으로 변환할 수 없다.
Parent parent = new Parent();
Child child = (Child) parent; // 강제 타입 변환할 수 없음
boolean result = 좌항(객체)
instanceof
우항(타입)
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발생!!