다형성 다시 한 번 복습합시다!!!
다형성: 하나의 타입에 대입되는 객체에 따라서 실행 결과가 다양한 형태로 나오는 성질
쉽게 말해서 객체를 갈아 끼우면서 새로운 값들을 얻어낼 수 있는 것...
인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라서 실행 결과가 달라진다.
클래스의 상속이나 인터페이스 둘 다 다형성을 구현할 수 있다. 인터페이스를 이용하는 다형성이 더 많음!
인터페이스를 사용해서 메소드를 호출하도록 코딩을 했다면 구현 객체를 교체하는 것이 훨씬 더 손쉽다. 객체만 교체하면 실행 결과를 훨씬 다양하게 만들 수 있는 것이다.
인터페이스 타입으로 매개 변수를 선언하면 메소드 호출 시 매개값으로 여러 가지 종류의 구현 객체를 줄 수 있어서 메소드 실행 결과가 다양하게 나온다. 이거시... 다형성.
예를 들어서 RemoteControl 이라는 인터페이스가 있고 Television과 Audio는 인터페이스를 상속받는 클래스라고 해보자.
public void useRemoteControl(RemoteControl rc) {...}
이렇게 인터페이스 타입으로 매개변수를 주면 useRemoteControl()
메소드의 실행 결과가 각각 다르게 나오는 것이다!
이렇게 말로 하면 사실 어려움... 예제를 보면서 이해하는 것이 빠릅니다.
구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환이다. 구현 클래스가 인터페이스를 상속받는 것이니까 부모클래스(인터페이스)를 사용할 수 있게 되니깐요!!!!
public interface A {
}
public class B implements A {
}
public class C implements A {
}
public class D extends B {
}
public class E extends C{
}
// B클래스와 C클래스는 인터페이스를 상속받고
// D와 E는 각각 B와 C의 자식 클래스이다.
public class PromotionEx {
public static void main(String[] args) {
// 객체 생성
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a; // 인터페이스 변수 선언
// 자동 형변환
a = b;
a = c; // 교체! (인터페이스의 주기능)
a = d;
a = e;
}
}
인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체도 인터페이스 타입으로 자동 변환이 가능하다.
public interface Tire {
// 추상 메소드
void roll();
}
public class HankookTire implements Tire {
@Override
public void roll() { // 인터페이스 구현
System.out.println("한국 타이어가 굴러갑니다.");
}
}
public class KumhoTire implements Tire {
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}
상속과 유사하지만 다른 점!
상속에서는 타이어 클래스 타입에 한국 타이어와 금호 타이어라는 자식 객체를 대입해서 교체할 수 있었다.
하지만 위는 타이어가 클래스 타입이 아니라 인터페이스며 한국 타이어와 금호 타이어는 자식 클래스가 아닌 구현 클래스이다. 공통적으로 타이어 인터페이스를 구현했기 때문에 둘 다 타이어 인터페이스에 있는 메소드를 가지고 있다. => 둘 다 교체 가능한 객체에 해당하는 것!
public class Car {
// 필드
Tire tire1 = new HankookTire(); // 자동 형변환 (한국타이어 객체 대입)
Tire tire2 = new HankookTire();
// 메소드
void run() {
tire1.roll();
tire2.roll();
}
}
위와같이 자동차를 설계할 때 필드 타입으로 타이어 인터페이스를 선언하면 필드값으로 한국 타이어 또는 금호 타이어 객체를 대입할 수 있게 되는 것이다.
public class CarEx {
public static void main(String[] args) {
Car myCar = new Car(); // Car에 대한 객체 생성
myCar.run(); // [한국 타이어가 굴러갑니다 한국 타이어가 굴러갑니다]
// 타이어 객체 교체 (기본 디폴트 값으로는 한국타이어를 박아놓고 다른거로 교체)
myCar.tire1 = new KumhoTire(); // 바꾸는 건 뒤에 객체만 바꿔주면 됨!
myCar.tire2 = new KumhoTire(); // 금호로 교체!
myCar.run(); // 바뀐 내용 출력됨 [금호 타이어가 굴러갑니다 금호 타이어가 굴러갑니다]
}
}
[출력 결과]
한국 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
금호 타이어가 굴러갑니다.
금호 타이어가 굴러갑니다.
자동형변환은 필드 값을 대입할 때도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
public class Driver {
void driver(Vehicle vehicle) { // 매개변수에 객체 (데이터 타입, 변수명)
// 2. 여기에 버스 객체가 매개변수로 들어오는것과 마찬가지
vehicle.run(); // 3. vehicle에 bus가 들어온거니까 오버라이드 된 run() 메소드가 실행되는 것
// 이거시... 다형성. 존나 중요합니다.
}
}
Driver클래스에 정의된 driver메소드는 Vehicle 타입의 매개변수가 선언되어 있다.
public interface Vehicle {
// 추상 메소드 (만들고 나면 무조건 자식 메소드에서 구현 해주어야 함. 오버라이딩)
void run();
}
인터페이스 타입의 Vehicle!
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
public class Taxi implements Vehicle{
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
구현 클래스인 Bus에서 Dirver의 drive() 메소드를 호출할 때 Bus 객체를 생성하면 매개값을 줄일 수 있다.
public class DriverEx {
public static void main(String[] args) {
Driver driver = new Driver(); // 객체 생성
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.driver(bus);
// [버스가 달립니다.] 1. 이게 실행되면 → Driver 클래스로 고고
driver.driver(taxi);
// [택시가 달립니다.]
}
}
driver.driver(bus);
이 부분에서 자동 형변환이 일어나는 것!!!
Vehicle vehicle = bus
이렇게!!! Vehicle을 구현한 Bus 객체가 매개 값으로 사용되는 거니까!!! 자동 형변환!!!
구현 객체가 인터페이스 타입으로 자동 변환하면, 인터페이스에 선언된 메소드만 사용 가능하다. (상속과 비슷하쥬?) 하지만 경우에 따라서 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생하는데 그럴 때 강제 형변환을 사용하는 것!
강제 형변환을 해서 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 필드와 메소드를 사용
public interface Vehicle {
void run();
}
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
public void chechFare() {
System.out.println("승차요금을 체크합니다.");
}
}
public class DriverEx {
public static void main(String[] args) {
Vehicle vehicle = new Bus(); // 객체 생성
vehicle.run();
//vehicle.chechFare(); 이렇게는 불가!
Bus bus = (Bus) vehicle; // 강제 형변환
bus.run();
bus.checkFare(); // 구현 클래스의 메소드 접근 가능!!!
}
}
[출력 결과]
버스가 달립니다.
버스가 달립니다.
승차요금을 체크합니다.
강제 형변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다. 구현 객체가 변환되어있는지 모르고 변환을 하면 에러가 발생한다!!
그럴 때는 상속에서도 사용한 instanceof
연산자를 사용하면 된다!
public class Driver {
public void driver(Vehicle vehicle) {
if(vehicle instanceof Bus) { // vehicle 매개 변수가 참조하는 객체가 Bus인지 확인... 그러니까 Bus 객체를 가지고 있냐고 묻는것!!!
Bus bus = (Bus) vehicle; // Bus 객체일 경우 안전하게 강제 형변환 ㄱㄱ
bus.checkFare(); // 그러면 이제 checkFare() 메소드에도 접근 가능! 강제형변환의 이유임
}
vehicle.run();
}
}