메소드 재정의 + 타입 변환 -> 다형성
타입 변환이란 타입을 다른 타입으로 변환하는 행위를 말합니다.
자동 타입 변환(promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말합니다.
자동 타입변환은 다음과 같은 조건에서 일어납니다.
부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
비록 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된다.
예외도 있는데, 메소드가 자식 클래스에서 재정의 되었다면 자식 클래스의 메소드가 대신 호출된다.
왜 자동 타입 변환이 필요한가?
그냥 자식 타입으로 사용하면 되는걸 왜 부모타입으로 변환해서 쓰는지..
다형성을 구현하기 위해서 -> 필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에
필드 사용 결과가 달라질수 있다. 이것이 필드의 다형성이다.
예제)
-------------------------------------------------------------------------------
package ch07;
public class Tire {
// 필드 (기본타입 필드 : 값을 저장)
public int maxRotation; //접근제한자가 public 인스턴스필드 : 최대 회전수를 저장하는 필드(타이어 수명)
public int accumulatedRotation; //접근제한자가 public 인스턴스필드 : 누적 회전수를 저장하는 필드
public String location; //접근제한자가 public 인스턴스필드 : 타이어 위치를 저장하는 필드
// 생성자 (매개변수 2개인 생성자 : 타이어 위치, 최대회전수를 초기화)
public Tire(String location, int maxRotation) {
this.location = location;
this.maxRotation = maxRotation;
}
// 메소드
public boolean roll() { //접근제한자가 public이고, return 타입이 boolean인 roll이라는 이름의 메서드
++accumulatedRotation; // 1씩 증가시켜서 누적회전수 필드에 저장
if(accumulatedRotation<maxRotation) { // 누적 회전수가 최대 회전수 보다 작으면
System.out.println(location + "Tire 수명 : " + (maxRotation-accumulatedRotation) + "회");
return true; //리턴타입과 return 키워드 뒤에 있는 타입이 일치
}else { // 그렇지 않으면 (누적 회전수가 최대 회전수와 같거나 크면)
System.out.println("***" + location + " Tire 펑크 ***");
return false; //리턴타입과 return 키워드 뒤에 있는 타입이 불일치
}
}
}
-------------------------------------------------------------------------------
package ch07;
public class Car1 {
// 필드 (참조타입 필드 : 주소 번지를 저장)
Tire frontLeftTire = new Tire("앞 왼쪽",6);
Tire frontRightTire = new Tire("앞 오른쪽", 2);
Tire backLeftTire = new Tire("뒤 왼쪽", 3);
Tire backRightTire = new Tire("뒤 오른쪽", 4);
//생성자
//메서드
int run() { // 접근제한자가 default이고, 리턴타입이 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() { // 접근제한자가 default이고, 리턴타입이 void인 run이라는 이름의 메소드
System.out.println("[자동차가 멈춥니다.]");
}
}
-------------------------------------------------------------------------------
package ch07;
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;
}
}
}
-------------------------------------------------------------------------------
package ch07;
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;
}
}
}
-------------------------------------------------------------------------------
package ch07;
public class CarExample {
public static void main(String[] args) {
Car1 car = new Car1();
for(int i = 1; i <= 5; i++) {
int problemLocation = car.run();
switch(problemLocation) {
case 1:
System.out.println("앞 왼쪽 Hankook로 교체");
car.frontLeftTire = new HankookTire("앞왼쪽", 15);
break;
case 2:
System.out.println("앞 오른쪽 KumHoTire로 교체");
car.frontRightTire = new KumhoTire("앞오른쪽 ", 13);
break;
case 3:
System.out.println("뒤 왼쪽 Hankook로 교체");
car.backLeftTire = new HankookTire("뒤왼쪽", 14);
break;
case 4:
System.out.println("뒤 오른쪽 KumhoTire로 교체");
car.backRightTire = new KumhoTire("앞왼쪽", 17);
break;
}
System.out.println("-----------------------------");
}
}
}
-------------------------------------------------------------------------------
자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 떄 많이 발생한다.
메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는것이 정석이지만, 매개값을 다양화 하기 위해
매개 변수에 자식 객체를 지정할 수도 있습니다.
ex)
예를 들어, 다음과 같이 Driver 클래스에는 drive() 메소드가 정의되어 있는데 Vehicle 타입의 매개 변수가 선언되어 있습니다.
class Driver{
void drive(vecicle){
vehicle.run();
}
}
drive() 메소드를 정상적으로 호출한다면 다음과 같을 것이다.
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(vehicle);
만약 Vehicle의 자식 클래스인 Bus 객체를 dirve() 메서드의 매개값으로 넘겨준다면 어떻게 될까요?
예제)
상속자체가 강제 참조타입.
자식 타입이 부모 타입으로 자동 타입 변환 후 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있습니다.
자식타입 변수 = (자식타입) 부모타입;
자식 타입이 부모 타입으로 자동 타입 변환하면 부모에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따른다.
만약 자식에 선언된 필드에 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환한 다음 자식의 필드와 메소드를 사용하면 된다.
강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에 다음과 같이 처음부터 부모 타입으로 생성된 객체는
자식 타입으로 변환할 수 없습니다.
Parent parent = new Parent();
Child child = (Child)parent (강제 타입 변환을 할 수 없음)
객체가 어떤 타입인지 조사할 때 instanceof 연산자를 사용합니다.
주로 강제 타입 변환 전에 변환이 가능한지 조사할 때 사용합니다.
// boolean result = 좌향(객체) instanceof 우향(타입)
-> 좌향의 객체가 우항의 인스턴스이면(우향의 타입으로 객체가 생성되었다면) true 아니면 false
만약 타입을 확인하지 않고 강제 타입 변환을 시도한다면 ClassCastException이 발생할 수 있다.