(JAVA) 다형성, 참조변수의 형변환, instanceof 연산자

InAnarchy·2022년 10월 21일
0

JAVA

목록 보기
16/18
post-thumbnail

Table of Contents

  • 다형성
  • 참조변수의 형변환
  • instanceof 연산자
  • 매개변수의 다형성
  • 여러종류의 객체를 배열로 다루기

다형성

  • 사용방법은 동일하지만 다양한 객체를 이용해 다양한 실행 결과가 나오도록 하는 성질
  • 다형성 구현을 위해서는 메서드 재정의와 타입 변환이 필요
  • 조상 타입 참조변수로 자손 타입 객체를 다루는 것
class Tv {
    boolean power;
    int channel;

    void power() {
        power = !power;
    }

    void channelUp() {
        ++channel;
    }

    void channelDown() {
        --channel;
    }
}

class smartTv extends Tv{
	String text;
    void caption(){
    }
}

위와 같은 코드에서, 우리는 인스턴스를 다루기 위해 인스턴스 타입과 일치하는 타입의 참조변수만을 사용했다. 즉

SmartTv s = new SmartTv(); 
Tv t = new Tv();

와 같이 사용했는데, Tv와 SmartTv클래스가 서로 상속관계에 있을 경우

Tv t = new SmartTv(); //Tv리모콘으로 SmartTv를 다룬다.

와 같이 조상 클래스 타입(TV)의 참조 변수(t)로 자손 클래스(SmartTv)의 인스턴스를 참조하는 것도 가능하다.

결론적으로는

smartTv s = new SmartTv(); //참조변수와 인스턴스 타입 일치
Tv t = new SmartTv();  //조상 타입 참조변수로 자손 타입 인스턴스 참조

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

그러나

SmartTv s = new Tv();

와 같이 자손 타입의 참조 변수로 조상 타입의 객체를 가리킬 수는 없다.

실제 인스턴스인 Tv의 멤버 개수보다 참조변수 s가 사용할 수 있는 멤버 개수가 더 많기 때문이다.
인스턴스 SmartTv의 기능이 7개인데 리모콘 t의 기능이 5개인건 괜찮다. 있는 기능을 안 쓰는건 괜찮은데
Tv 인스턴스의 기능이 5개인데 리모콘 s의 기능이 7개이면 안된다. 기능을 사용할 수 없기 때문이다.

다형성 QNA

q. 참조변수의 타입은 인스턴스의 타입과 반드시 일치해야하는가?
a. 일치하는 것이 보통이지만 일치하지 않을 수도 있다.

q. 참조변수가 조상타입일 때와 자손타입일 때의 차이는?
a. 참조변수로 사용할 수 있는 멤버의 개수가 달라진다.

SmartTv리모콘으로는 7개 사용 가능, Tv리모콘으로는 5개 사용가능

q. 자손타입의 참조 변수로 조상타입의 객체를 가리킬 수 있는가?
a. ㄴㄴㄴ

Tv t = new SmartTv(); //OK, 조상타입(Tv)의 참조변수(t)로 자손타입(SmartTv) 가리킴
SmartTv s = new Tv(); //NOOOOO 

참조변수의 형변환

  • 사용할 수 있는 멤버의 개수를 조절하는 것
  • 조상 자손 관계의 참조변수는 서로 형변환 가능
class Car{}
class FireEngine extends Car{}
class Ambulance extends Car{}

위와 같은 Car클래스, 그리고 이를 상속받는 FireEngine,Ambulance클래스가 있다고 할 때

Car c = (Car)f; //조상인 Car타입으로 형변환(업캐스팅,생략가능)
FireEngine f2 = (FireEngine)c; //자손인 FireEngine타입으로 형변환(다운캐스팅, 생략불가)
Ambulance a = (Ambulance)f; //에러, 상속관계가 아닌 클래스 간 형변환 불가

class Car {
    String color;
    int door;

    void drive() {
        System.out.println("drive");
    }

    void stop() {
        System.out.println("Stop");
    }
}

class FireEngine extends Car{
    void water(){
        System.out.println("water");
    }
}
public class Poly {
    public static void main(String[] args) {
        Car car = null; //초기화는 했으나 아무것도 저장하는 것이 없음, 참조하는 곳이 없음 == 객체가 없음.
        FireEngine fe = new FireEngine();
        FireEngine fe2 = null;

        fe.water();
        car = (Car)fe; //생략가능
        //참조변수 fe의 값을 car에 저장해서 car로도 FireEngine인스턴스를 다룰 수 있다.
        //그러나 car로는 water()를 다룰 수 없다.
        //car.water(); //호출불가
        fe2 = (FireEngine)car; //생략불가
        fe.water();
    }
}

그런데 만약에 아래와 같이 참조변수 = null 일 때,

Car car = null;
FireEngine fe = null;

FireEngine fe2 = (FireEngine)car;
Car car2 = (Car)f2;

즉 객체가 없을 때에도 형변환에는 문제가 없다.
그런데 멤버변수를 사용하려고 하면 NullPointerException이 발생한다. 객체가 없을 때 메서드를 호출할 수 없기 때문이다.

Car car = null;
FireEngine fe = null;

FireEngine fe2 = (FireEngine)car;
Car car2 = (Car)f2;
null.drive(); //에러

다음 예시를 보자.

Car c = new Car();
FireEngine fe = (FireEngine) c;
fe.water();

이 경우에는 ClassCastException이 발생한다.
Car타입의 참조변수 c는 4개만 사용할 수 있는데
fe도 c가 가리키는 Car객체를 가리키고 있다.
이 때 Car에는 water()가 없기 때문에 Water메서드를 호출하게 되면 에러가 발생한다.

즉 참조변수 간의 형변환 자체가 중요한게 아니라, 실제 인스턴스가 무엇인지가 중요하다.

instanceof 연산자

  • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용하는 연산자
  • 왼쪽에는 참조변수, 오른쪽에는 타입(클래스명)이 위치
  • 연산결과가 true라면 참조변수가 검사한 타입으로 형변환이 가능하다는 뜻
void doWork(Car c){ //Car c의 자리에는 Car의 자손들도 들어올 수 있다.
	if(c instanceof FireEngine){ //형변환이 가능한지 확인, c가 가리키는 객체가 FireEngine인지 
    	FireEngine fe = (FireEngine)c; //형변환
        fe.water();

Car타입 리모콘인 c로는 water()를 호출할 수 없으므로
리모콘을 FireEngine타입으로 바꿔서 호출한다.

class Car {
    String color;
    int door;

    void drive() {
        System.out.println("drive");
    }

    void stop() {
        System.out.println("Stop");
    }
}

class FireEngine extends Car{
    void water(){
        System.out.println("water");
    }
}
public class Poly{
    public static void main(String[] args) {
        FireEngine fe= new FireEngine();
        System.out.println(fe instanceof Object);
        System.out.println(fe instanceof Car);
        System.out.println(fe instanceof FireEngine);
    }
}
output
true
true
true

매개변수의 다형성

  • 참조형 매개변수는 메서드 호출 시 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.
  • 조상 타입의 참조변수를 사용해서 하나의 메서드로 여러 타입의 객체를 사용할 수 있다.
class Vehicle{
    public void run(){
        System.out.println("차량이 달립니다");
    }
}
class Driver{
    public void drive(Vehicle vehicle){ //Vehicle타입의 매개값을 받음
        vehicle.run();
    }
}

class Bus extends Vehicle{
    @Override
    public void run(){
        System.out.println("버스가 달립니다");
    }
}

class Taxi extends Vehicle{
    @Override
    public void run(){
        System.out.println("택시가 달립니다");
    }
}

public class DriverEx {
    public static void main(String[] args) {
        Driver driver = new Driver();
        Bus bus = new Bus();
        Taxi taxi = new Taxi();

        driver.drive(bus); //자동 타입 변환: Vehicle vehicle = bus;
        driver.drive(taxi);  //자동 타입 변환: Vehicle vehicle = taxi;
    }
}
버스가 달립니다
택시가 달립니다
class Product {
    int price;
    int bonusPoint;
} //제품

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

class Buyer {
    int money = 1000;
    int bonusPoint = 0;
//고객

//물건 구입
    void buy(Tv t) {
        money = money - t.price;
        bonusPoint = bonusPoint + t.bonusPoint;

    }

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

이 코드에서 계속 오버로딩을 하고 있는데, 구입하는 종류가 늘어날 떄 마다 새로운 buy 메서드를 추가해야한다.buy(Phone p), buy(ipad i)처럼..

이 때 buy메서드에 Tv타입, Computer타입 대신 Product타입을 사용하고
조상타입의 참조변수로 자손 객체를 가리키면 된다.

void buy(Product p){
	money -= p.price;
    bonusPoint += p.bonusPoint;
}
Product p1 = new Tv(); //조상 참조변수로 자손 객체 가리킴
Product p2 = new Computer();
class Product {
    int price;
    int bonusPoint;

    Product(int price) {
        this.price = price;
        bonusPoint = (int) (price / 10.0);
    }
}

class Tv1 extends Product{
    Tv1(){
        //조상클래스의 생성자 Product(int price)를 호출
        super(100);
    }
    public String toString(){
    return "Tv";
    }
}
class Computer extends Product {
    Computer() {
        super(200);
    }

    public String toString() {
        return "Computer";
    }
}

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

    void buy(Product p) { //new Tv1(), new Computer(). 조상타입의 참조변수 p가 자식 객체들을 가리킨다.
        if (money < p.price) {
            System.out.println("잔액 부족");
            return;
        }

        money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println(p + "을/를 구입"); //p.toString()과 같음
    }
}

class Poly {
    public static void main(String[] args) {
        Buyer b = new Buyer();

        b.buy(new Tv1()); //buy(Product p)
        b.buy(new Computer()); //buy(Product p)

        System.out.println("현재 남은 돈은 " + b.money + "만원");
        System.out.println("현재 보너스 점수는 " + b.bonusPoint + "점");
    }
}

여러종류의 객체를 배열로 다루기

  • 하나의 배열에 여러 종류 객체를 저장할 수 있다.
  • 조상타입의 배열에 자손들의 객체를 다룰 수 있다.

조상 Product의 참조변수로 여러 자손들을 가리키는

Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();

를 배열로 바꾸면 다음과 같다

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();
class Product {
    int price;
    int bonusPoint;

    Product(int price) {
        this.price = price;
        bonusPoint = (int) (price / 10.0);
    }
    Product(){}
}

class Tv1 extends Product{
    Tv1(){
        //조상클래스의 생성자 Product(int price)를 호출
        super(100);
    }
    public String toString(){
    return "Tv";
    }
}
class Computer extends Product {
    Computer() {
        super(200);
    }

    public String toString() {
        return "Computer";
    }
}

class Buyer {
    int money = 1000;
    int bonusPoint = 0;
    Product[] cart = new Product[10]; //구입한 물건을 담을 카트
    int i = 0; //카트 인덱스

    void buy(Product p) {
        if (money < p.price) {
            System.out.println("잔액 부족");
            return;
        }

        money -= p.price;
        bonusPoint += p.bonusPoint;
        cart[i++] = p;
        System.out.println(p + "을/를 구입");
    }

    void summary() {
        int sum = 0;
        String itemList = "";

        for (int i = 0; i < cart.length; i++) {
            if (cart[i] == null)
                break;
            sum += cart[i].price;
            itemList += cart[i] + ", ";
        }
        System.out.println("총 금액은 " + sum + "만원");
        System.out.println("구입한 제품은 " + itemList);
    }
}

class Poly {
    public static void main(String[] args) {
        Buyer b = new Buyer();

        b.buy(new Tv1());
        b.buy(new Computer());
        b.summary();
    }
}
Tv을/를 구입
Computer을/를 구입
총 금액은 300만원
구입한 제품은 Tv, Computer, 
profile
github blog 쓰다가 관리하기 귀찮아서 돌아왔다

0개의 댓글