다형성한 타입의 조변수로 여러 타입의 객체를 참조할 수 있도록 하는 것이다.

다시말하자면

조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하는것

다음과 같은 예제를 보자

class Car {
    private String engine;

    void turnOn()
    void turnOff()
    String getEngine()
    void setEngine(String engine)
}

class ElectroCar extends Car{
    String battery;
    void chargeBattery()
}

이런경우는 ElectorCarCar를 상속하고있다.

이럴때 인스턴스 객체를 다음과 같이 생성할 수 있다.

// 1번
Car car = new ElectroCar();
// 2번
ElectroCar= new ElectroCar();

이렇게 상속하는 관계일 경우에는 부모 클래스 타임의 참조변수로 자식 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

이렇게 상속하는 경우는 어떤 차이점이 있냐면

둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

저 예시로 cartesla는 사용할 수 있는 멤버의 개수가 다르다.

  • teslaElectroCar의 멤버인 batterychangeBatttery()메서드를 사용이 가능하다.
  • carbatterychageBattery()의 메서드 사용이 불가능하다.

그렇다면 이렇게 생각할 수도 있을 것이다.

자식 타입이 참조변수로 조상타입의 인스턴스를 참조하는 케이스이다.

ElectroCar car = new Car();

이러한 경우에는 항상 생각해야할 것이 있다.

클래스는 상속을 통해서 확장이 가능하다 하지만 축소는 될 수가 없다.
부모 인스턴스의 개수는 자식 인스턴스의 멤버의 개수보다 같거나 적어야한다.

부모타입의 참조변수 → 자식타입의 인스턴스 참조 가능

자손타입의 참조변수 → 부모타입의 인스턴스 참조 불가능

참조변수의 형변환

참조변수도 형변환이 가능하다 형변환할 수 있는 조건은 다음과 같다.

  • 서로 상속관계에 있는 클래스만 가능하다.
  • 자식타입의 참조변수를 부모타입의 참조변수로 하는것이 가능하다.
  • 부모 타입의 참조변수를 자식타입의 참조변수로 형변환이 가능하다.
  • 자식타입의 참조변수를 부모타입의 참조변수로 형변환 할 때에는 작은 자료형에서 큰 자료형으로 형변환 하는 것이므로 생략이 가능하다.

형변환의 예시를 보자

Car car = null;
ElectroCar tesla1 = new ElectroCar();
ElectroCar tesla2 = null;

car = tesla1; // 작은것에서 큰것을 형변환 함으로 형변환 생략 (업캐스팅)
tesla2 = (ElectroCar)car; // 큰것에서 작은것으로 변환이므로 형변환 필요 (다운캐스팅)

이러한 두번째처럼 작은 곳에서 큰곳으로의 형변환은 생략할 수 없기 때문에

instanceof연산자를 사용하여 인스타입이 어떤것인지 확인하는 것이 안전하다.

이러한 형변환을 했을 때 틀리기 쉬운 오류들이 있다. 다음 예시를 보자

Car car = null;
ElectroCar tesla = new ElectroCar();

car = tesla;
car.chargeBattery(); // -> 불가능 하다. 

이러한 결과가 불가능한 이유는 형변환은 단지 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문이다

carCar인스턴스이기 때문에 메서드 호출이 불가능한것이다.

만약에 이런경우를 보자

Car car = null;
ElectroCar tesla1 = new ElectroCar();
ElectroCar tesla2 = null;

car = tesla;

tesla2 = (ElectroCar)car;
tesla2.chargeBattery(); // -> 가능하다.

이는 인스턴스가 tesla2가 형변화된 car를 받고 ElectroCar의 인스턴스이기 때문에 메서드를 사용 가능하다.

다운캐스팅을 함으로서 car에서 사용할 수 없었던 ElectroCar 의 기능을 사용할 수 있는 것이다.

정리하자면

  • 서로 상속관계에 있는 타입간의 형변환은 양방향 자유롭게 수행될 수 있다.
  • 참조 변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
  • 참조변수가 가리키는 인스턴스의 타입이 무엇인지 instanceof 를 사용해서 확인하는 것이 중요한다.
  • 컴파일 시에는 참조변수간의 타입만 확인하기 때문에 오류가 없지만 메서드를 호출하거나 ㅎ라때는 에러가 발생할 수 있다.

Instanceof연산자

instaceof연산자는 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아볼 수 있는 연산자이다.

bool형으로 연산의 결과를 반환한다.

  • ture가 나왔을 경우는 참조변수가 검사한 타입으로 형변환이 가능하다.
  • false가 나왔을 경우는 형변환이 불가능하다는 것이다.
ElectroCar tesla = new ElectroCar();

System.out.println(tesla instanceof ElectroCar); //true

System.out.println(tesla instanceof Car); //true

System.out.println(tesla instanceof Object); //true

tesla는 당연히 CarObject클래스를 다중상속 하였기 때문에 부모타입의 연산에 true를 결과로 얻는다.

참조변수와 인스턴스의 연결

우리는 형변환을 배워서 부모클래스던지 자식클래스로 참조변수의 타입을 변경할 수 있다.

만약에, 자식클래스와 부모클래스에 똑같은 변수가 있다고 생각해보자

인스턴스는 다음과 같이 2개를 생성하였다.

class Car{
    String name = "Parent car";
    void whoseCar(){
        System.out.println("Parent car");
    }
}

class ElectroCar extends Car{
    String name = "Child car";
    void whoseCar(){
        System.out.println("Child car");
    }
}

public static void main(String args){
    Car car = new ElectroCar();
    ElectroCar tesla = new ElectroCar();

    car.whoseCar(); // Child Car
    tesla.whoseCar(); // Child Car

    System.out.println(car.name); // Parent Car
    System.out.println(tesla.name); // Child Car
}

조금신기한 결과가 나타났다.

결과적으로 부모 클래스와 자식 클래스에 중복으로 정의됬을 경우

부모타입의 참조변수를 사용했을 경우는 조상클래스의 선언된 멤버변수가 사용된다.

하지만 메서드의 경우에는 참조변수의 타입에 관계 없이 인스턴스의 메서드(오버라이딩된 메서드)가 호출된다.

매개변수의 다형성

다형성은 클래스에만 국한하지 않고 메서드의 매개변수에서도 적용이 된다.

물품들을 구매하는 예제를 보자.

class Product{
    int price;
    int bonusPoint;
}

class Tv extends Product {}
class Computer extends  Product {}
class Audio extends Product {}

class Buyer{
    int money = 10000;
    int bonusPoint = 0;
}

여기서 구매를 한다는 표시로 buy()라는 메서드를 다음과 같이 만들것이다.

buy()는 일반적으로 Tv, Computer, Audio마다 각각 price가 다르기 때문에 이러한 buy()메서드를 총 3개를 만들어야할것이다.

class Buyer{
    int money = 10000;
    int bonusPoint = 0;

    void buy(Tv t){
        money -= t.price;
        bonusPoint += t.bonusPoint;
    }

    void buy(Computer c){
        money -= c.price;
        bonusPoint += c.bonusPoint;
    }

    void buy(Audio a){
        money -= a.price;
        bonusPoint += a.bonusPoint;
    }
}

이런식으로 총 3개의 매개변수가 다른 buy매서드를 오버로딩 해야할 것이다.

하지만 매개변수의 다형성을 이용한다면 다음과 같이 하나의 메서드로 줄일 수 있다.

class Buyer{
    int money = 10000;
    int bonusPoint = 0;

    void buy(Product p){
        money -= p.price;
        bonusPoint += p.bonusPoint;
    }

}

왜냐하면 제품들은 모두 Product를 상속받았기 때문이다.

이런방식으로 모든 클래스의 부모클래스는 Object이기 때문에 이를 활용한 예제도 가능하다는 것을 잊지 말자

또한, 이러한 객체들을 배열로 다룰수 있다.

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

이런식으로 부모타입인 Product 타입의 배열을 사용함으로서 상속한 자식들을 하나의 배열로 쉽게 이용할 수 있다.

Reference

  • 자바의 정석

'Java > Java' 카테고리의 다른 글

[Java/Study] 인터페이스  (0) 2021.09.11
[Java/Study] 추상클래스  (0) 2021.09.11
[Java/Study] 제어자  (0) 2021.09.09
[Java/Study] Package 와 Import 및 터미널로 자바 실행방법  (0) 2021.09.08
[Java/Study] 오버라이드  (0) 2021.09.07

+ Recent posts