자바의 정석 ch7.5 다형성

해로(김선호)·2022년 1월 28일
0

Java 문법

목록 보기
2/15

📔 본 포스팅은 자바의 정석(남궁성 저, 3판)을 읽고 정리한 글입니다.

ch7.5 다형성 (polymorphism)

5.1 다형성이란?

  • 사전적 의미 : 여러 가지 형태를 가질 수 있는 능력

객체지향개념에서의 다형성

  • 조상 타입 참조 변수로 자손 타입 객체를 다루는 것(= 타입 불일치. 조상과 자손의 관계에서만 인스턴스 생성 시 타입 불일치가 가능)

객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이(참조변수가 조상타입일 때와 자손타입일 때의 차이)

// SmartTv클래스는 Tv클래스의 자손 클래스
SmartTv s = new SmartTv(); // 참조 변수와 인스턴스 타입이 일치
Tv      t = new SmartTv(); // 조상 타입 참조변수로 자손 타입 인스턴스 참조
  • 💯💯💯 참조변수로 사용할 수 있는 멤버의 개수가 달라진다.
    1. 자손 타입으로 선언하는 경우 (타입 일치)
      • 실제 멤버의 개수와 사용가능한 멤버의 개수가 일치
      • 상속받은 멤버와 자손에서 선언한 멤버 모두 사용가능
    2. 조상 타입으로 선언하는 경우 (타입 불일치)
      • 실제 멤버의 개수와 사용가능한 멤버의 개수가 다름
      • 상속받은 멤버(조상 멤버)만 사용가능
      • 자손에서 선언한 멤버는 사용 불가능

자손 타입 참조 변수로 조상 타입의 객체를 참조할 수 없다

// SmartTv클래스는 Tv클래스의 자손 클래스
Tv      t = new SmartTv(); // OK. 허용
SmartTv s = new Tv(); // 에러. 허용 안 됨
  1. 조상 타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
  2. 반대로 자손 타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

결국, 다형성이란 ‘조상 타입으로 선언된 참조변수로는 조상 멤버에만 접근 가능함’을 나타냄

5.2 참조변수의 형변환

참조변수의 형변환

  • 사용할 수 있는 멤버의 개수를 조절하는 것(리모콘 변경)
    • 참조변수에 할당된 주솟값이라든지, 가리키던 인스턴스가 변경된다든지 하는 것이 아니라 ‘멤버의 개수’만 조절하는 것임
  • 상속관계의 참조변수끼리만 서로 형변환 가능
    • 상속관계가 아닌 클래스(예 : 형제 관계의 클래스)는 형변환 불가능
class Car { ... } // 4개의 멤버 
class FireEngine extends Car { ... } // 4개의 조상멤버, 1개의 자손 멤버
class Ambulance extends Car { ... }

// ...
FireEngine f = new FireEngine(); // FireEngine인스턴스 생성
Car c = (Car)f; // OK. 조상인 Car타입으로 형변환(생략가능), c는 f가 가리키던 FireEngine인스턴스를 가리킴
FireEngine f2 = (FireEngine)c; // OK. 자손인 FireEngine타입으로 형변환(생략불가)
Ambulance a = (Ambulance)f; // 에러. 상속관계가 아닌 클래스 간의 형변환은 불가능

형변환을 생략 가능한 지 불가능한 지 따질 필요 없이 그냥 전부 형변환을 명시하라!
→ 타입이 안 맞으면 형변환 해주면 그 뿐.

  • 대입 연산자를 쓰려면, 좌변과 우변의 타입이 일치해야하기 때문에 형변환을 해주는 것임
  • 위 코드에서, 참조변수 cf, f2 모두 같은 객체(FireEngine인스턴스)를 가리키지만(같은 주소값을 가지지만),
    • 참조변수 f, f2는 FireEngine타입(자손 타입)으로 5개의 멤버 모두 접근 가능하고
    • 참조변수 c는 Car타입(조상 타입)으로 4개의 멤버(조상 멤버)에만 접근 가능
  • 사용할 멤버의 개수를 줄이는 것은 안전하다. (→ 조상 타입으로의 캐스팅이 생략 가능한 이유)
  • 사용할 멤버의 개수를 늘리는것은 불안전하다.(→ 자손 타입으로의 캐스팅이 생략 불가능한 이유)

💯💯💯형변환이 중요한게 아니라 실제 가리키는 인스턴스가 무엇인지가 중요하다.

🚩결론
참조변수가 가리키는 실제 객체가 무엇인지를 꼭 확인하고, 그 멤버의 개수를 초과하면 안 된다.

  • 형변환은 상속관계이기만 하면 된다. 심지어 참조변수가 null 이라도 형변환이 가능하다.
  • 참조변수가 가리키는 실제 객체의 멤버 개수가 중요하다.
    • 실제 객체의 멤버 개수 이내에서 줄이고 늘리는 것은 상관없음
    • 객체의 멤버 개수를 넘어가면 실행시 에러가 발생함(java.lang.ClassCastException)
public class Ex7_7 {
    public static void main(String[] args) {
        Car c = new Car();
        // 1. 상속관계이기 때문에 어찌됐든 형변환은 가능 
        // 2. Car의 멤버 개수는 4개인데 FireEngine으로 5개를 사용하려 함
        // 3. 실제 객체의 멤버 개수(4개)를 초과했으니 런타임 에러 발생
        FireEngine fe = (FireEngine) c; // 형변환 런타임 에러 java.lang.ClassCastException
        // FireEngine리모컨에 버튼이 있으니까 water()버튼이 눌리기는 함(컴파일은 됨)
        fe.water(); // 컴파일 OK.
    }
}

class Car {
    String color; // 인스턴스 생성 시 null로 초기화 됨
    int door; // 인스턴스 생성 시 0으로 초기화됨

    void drive() { }

    void stop() { }
}

class FireEngine extends Car { // 소방차
    void water() {
        System.out.println("water!!!");
    }
}

5.3 instanceof 연산자

  • 참조변수의 형변환 가능 여부 확인에 사용하는 연산자. 가능하면 true반환
  • 형변환 전에 반드시 instanceof로 확인해야함
class FireEngine extends Car { ... }

void doWork(Car c) {
	if (c instanceof FireEngine) { // 1. c가 FireEngine으로 형변환이 가능한지 확인
		FireEngine fe = (FireEngine) c;  // 2. 형변환
		fe.water();
		...
	} else if (c instanceof Ambulance){ // 1. 형변환 가능한지 확인
		Ambulance a = (Ambulance) c; // 2. 형변환
		a.siren();
		...
	}
  • 위 코드에서 doWork( ) 메서드를 호출할 때, 매개변수로 Car클래스 또는 그 자손 클래스의 인스턴스를 넘겨주는 것이 가능.
    • ‘doWork(new Car());’ / ‘doWork(new FireEngine());’ / ‘doWork(new Ambulance());’ 이렇게 세 가지의 doWork( ) 메서드의 호출이 모두 가능하다.
    • ‘doWork(new FireEngine());’ 은 ‘Car c =new FireEngine(); doWork(c);’ 와 동일함
  • 참조변수의 형변환을 하는 이유
    • 참조변수(리모콘)을 변경함으로써 사용할 수 있는 멤버의 갯수를 조절하기 위해서 (인스턴스의 원래 기능을 모두 사용하려고)
    • Car타입의 리모콘인 c로는 water( )를 사용할 수 없으므로, 리모콘을 FireEngine타입으로 바꿔서 water( ) 를 호출
class FireEngine extends Car { }

FireEngine fe = new FireEngine();
System.out.println(fe instanceof Object); // true
System.out.println(fe instanceof Car); // true
System.out.println(fe instanceof FireEngine); // true
  • 조상 클래스자기 자신에 대한 instanceof 연산 결과는 true이다.
    • Object클래스는 최고 조상 클래스. 당연히 형변환이 가능하다.

🚩 instanceof의 핵심 2가지

  1. (1) instanceof로 형변환 가능한지 확인하고 (2) 형변환을 해야한다.
  2. 조상 클래스와 자기 자신에 대한 instanceof 연산 결과는 true이다.

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

  • 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 참조변수의 타입 (조상 또는 자손)에 따라 사용되는 멤버변수가 다르다.
    • 조상 타입의 참조변수를 사용할 경우에는 조상 클래스에 선언된 멤버변수가 사용됨.
    • 자손 타입의 참조변수를 사용할 경우에는 자손 클래스에 선언된 멤버변수가 사용됨.
  • 멤버변수가 중복으로 정의되지 않은 경우에는 참조변수에 따른 변화는 없다.
  • 메서드의 경우 자손 클래스에서 조상 클래스의 메서드를 오버라이딩한 경우에는 참조변수의 타입과 관계없이 오버라이딩된 메서드(실제 인스턴스의 메서드)가 사용된다.

💯 멤버변수들을 주로 private으로 외부로부터의 직접 접근을 제한하고, 외부에서는 메서드를 통해 간접 접근을 할 수 있게 코드를 작성하라.
→ 애초에 참조변수를 통해 사용되는 인스턴스 변수가 달라지는 일이 없게 코드를 작성한다.

5.5 매개변수의 다형성

다형성의 장점

  1. 다형적 매개변수를 사용할 수 있다
  2. 하나의 배열로 여러 종류의 객체를 다룰 수 있다.

매개변수의 다형성

  • 참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 매개 변수로 넘겨줄 수 있다.
class Product {
    int price; // 제품가격
    int bonusPoint; // 제품구매 시 제공하는 보너스 점수

    Product(int price) { // 생성자
        this.price = price;
        bonusPoint = (int) (price / 10.0); // 보너스 점수는 제품가격의 10%
    }
}

class Tv extends Product {
    Tv() { // 조상클래스의 생성자 Product(int price) 호출
        super(100); // Tv의 가격을 100만원으로 함
    }

    public String toString() { return "Tv"; } // Object클래스의 toString()을 오버라이딩
}

class Computer extends Product {
    Computer() { super(200); }

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

class Buyer { // 고객
    int money = 1000; // 소유 금액
    int bonusPoint = 0; // 보너스 점수

    void buy(Product p) { // 매개변수의 다형성
        if (money < p.price) {
            System.out.println("잔액 부족");
            return;
        }

        money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println(p +  "을/를 구입하셨습니다."); // p.toString()을 사용한 것과 완전히 같은 연산임
        // System.out.println(p.toString() +  "을/를 구입하셨습니다.");
    }
}
public class Ex21_PolyArgumentTest {
    public static void main(String[] args) {
        Buyer b = new Buyer();

        b.buy(new Tv());
        b.buy(new Computer());

        System.out.println(b.money);
        System.out.println(b.bonusPoint);
    }
}
  • Buyer클래스의 buy(Product p) 메서드를 호출시 매개변수의 다형성이 사용된다.
    • 참조형 매개변수 p의 타입이 Product이기 때문에, 매개변수로 자손타입의 인스턴스를 매개변수로 사용하였음 ( ‘b.buy(new Tv());’ / ‘b.buy(new Computer());’ )

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

  • 조상 타입의 배열로 자손들의 객체를 담을 수 있다.
class Buyer2 {			  
	int money = 1000;	  
	int bonusPoint = 0;
	Product2[] cart = new Product2[10];  // 자손 타입의 객체를 저장할 수 있는 배열
	int i =0;		

	void buy(Product2 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] + ", "; // cart[i].toString()과 같음
		}
		System.out.println("구입하신 물품 총금액은 " + sum + "만원입니다.");
		System.out.println("구입하신 제품은 " + itemList + "입니다.");
	}
}

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

		b.buy(new Tv2());
		b.buy(new Computer2());
		b.buy(new Audio2());
		b.summary();
	}
}

가변 배열 vector 클래스

  • vector 클래스에는 Object[] 배열이 있어서 모든 종류의 객체 저장이 가능함
profile
Every Run, Learn Counts.

0개의 댓글