Java 다형성

I C-AN·2021년 7월 27일
0

Java

목록 보기
12/26
post-thumbnail

다형성

부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있다
단 부모 클래스가 자식 클래스를 참조할 때 부모 자신의 멤버와 오버라이딩 메서드만 접근할 수 있다

참조변수의 다형성

class Parent { ... }

class Child extends Parent { ... }

Parent pa = new Parent(); // 허용

Child ch = new Child();   // 허용

Parent pc = new Child();  // 허용

Child cp = new Parent(); // 에러

클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없으므로, 자식 클래스에서 사용할 수 있는 멤버의 개수가 언제나 부모 클래스와 같거나 많게 된다
때문에 자식 클래스 타입의 참조 변수로 부모 클래스 타입을 참조할 수 없게 된다

참조변수의 형변환

class Phone {
	void brand() {
		System.out.println("Galaxy");
	}
}

class Note9 extends Phone {
	void merit() {
		System.out.println("Classic, Wide");
	}
}

class S2 extends Phone {}

Phone phone = null;
Note9 note = new Note9();
Note9 note2 = null;
S2 s = null;

note.merit();
phone = (Phone) note; // 자식 타입을 부모 타입으로 형변환
// phone.merit(); // 에러! 부모 타입은 자식의 멤버를 호출할 수 없음 (오버라이딩 제외)
note2 = (Note9) phone; // 부모 타입을 자식 타입으로 형변환
note2.merit();
// s = (Note9)note; // 상속 관계가 아닌 참조변수는 형변환 불가

참조변수의 형변환은 사용할 수 있는 멤버의 개수를 조절하는 방법이다
부모 자식 관계의 참조변수는 서로 형변환이 가능하지만 상속 관계가 아닌 참조 타입끼리는 형변환을 할 수 없다

instanceof 연산자

System.out.println(phone instanceof Phone); // true
System.out.println(phone instanceof Note9); // false

// 자식이 조상으로 형변환 가능여부
System.out.println(note instanceof Object); // true
System.out.println(note instanceof Phone); // true
System.out.println(note instanceof Note9); // true

참조변수의 형변환 가능여부를 확인할 때 사용하며 boolean 값을 반환한다
작업 중 형변환을 할 때 전에 반드시 instanceof로 확인하는 과정을 거쳐야 한다

매개변수의 다형성

// 주문 앱 시나리오
class Food {
	int price;
	int bonuspoint;
	Food() {}
	Food(int price) {
		this.price = price;
		this.bonuspoint = (int) (price / 10.0);
	}
}

class Chicken extends Food {
	Chicken() { super(20000); } // Food(int price) 생성자를 호출
	public String toString() { return "Chicken"; }
}

class Pizza extends Food {
	Pizza() { super(15000); }
	public String toString() { return "Pizza"; }
}

class Order {
	int money;
	int bonuspoint;

	/*
	void buy(Chicken chicken) {}
	void buy(Pizza pizza) {}
	*/
    
	// 부모 타입인 Food 타입으로 자식 타입인 Chicken, Pizza를 받아올 수 있음
	void buy(Food food) {
	this.money -= food.price; // 가진 돈에서 음식의 가격을 뺀다
	this.bonuspoint += food.bonuspoint; // 음식 가격만큼의 보너스를 추가한다

	System.out.println("구매한 음식 : " + food.toString());
	System.out.println("잔액 : " + this.money);
	System.out.println("포인트 현황 : " + this.bonuspoint);
}

참조형 매개변수는 메서드 호출 시, 자신과 같은 타입 또는 자식타입의 인스턴스를 넘길 수 있다 상속관계에 있는 매개변수를 사용할 때 반복되는 코드를 줄이는 데 효과적이다

여러 객체를 하나의 배열로 다루기

// 부모타입의 배열, 자식타입의 요소
Food f[] = new Food[2];
f[0] = new Chicken();
f[1] = new Pizza();

부모타입의 배열에 자식들의 객체를 담을 수 있다

추상 클래스

abstract class Player { // 추상 클래스
	// 인스턴스 변수
	boolean pause;
	int currentPos;

	// 추상 클래스도 생성자가 있어야 한다
	Player() {
		pause = false;
		currentPos = 0;
	}

	// 추상 메서드 (상속을 통해 완성시키도록)
	abstract void play(int pos);
	abstract void stop();
    
	// 인스턴스 메서드
	void play() {
		play(currentPos); // 추상 메서드를 호출할 수 있다
	}

}

class AudioPlayer extends Player { // 상속을 통해 구현
	// 추상 메서드를 구현
	void play(int pos) {} 
	void stop() {}
}

// 추상 메서드를 모두 구현하지 않았으므로 추상 클래스
abstract class AbstractPlayer extends Player {
	void play(int pos) {}
	// abstract void stop()이 생략되어 있음
}

Player p = new Player(); // 에러! 추상 클래스의 인스턴스 생성 불가
Audioplayer ap = new AudioPlayer();
Player ap2 = new AudioPlayer(); // 다형성의 특징인 부모 타입의 자식 인스턴스 참조 이용

추상 클래스

  • 추상(미완성) 메서드를 갖고 있는 클래스
  • abstract 키워드를 사용한다
  • 다른 클래스 작성에 도움을 주기 위한 것으로 객체를 생성할 수 없다
  • 상속을 통해 추상 메서드를 완성해야 객체를 생성할 수 있다

추상 메서드

  • 미완성 메서드로 구현부 {}가 없는 메서드
  • 추상 메서드는 호출이 가능하다 (호출할 때는 선언부만 필요, 상속을 통해 자식이 완성)

추상 메서드의 사용 목적

추상 메소드가 포함된 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위함이다
만약 일반 메소드로 구현한다면 사용자에 따라 해당 메소드를 구현할 수도 있고, 안 할 수도 있다
하지만 추상 메소드가 포함된 추상 클래스를 상속받은 모든 자식 클래스는 추상 메소드를 구현해야만 인스턴스를 생성할 수 있으므로, 반드시 구현하게 됩니다

추상 클래스의 작성

abstract class Unit {
	// 공통으로 사용되는 부분
	int x, y;
	abstract void move(int x, int y); // 추상 메서드
	void stop() {} // 현재 위치에 정지
}

class Marine extends Unit {
	void move(int x, int y) {} // 추상 메서드 구현
	void stimPack() {} // 스팀팩 사용
}

class Tank extends Unit {
	void move(int x, int y) {}
	void changeMode() {} // 공격모드 변환
}

class Dropship extends Unit {
	void move(int x, int y) {}
	void load() {} // 선택된 대상을 태운다
	void unload() {} // 선택된 대상을 내린다
}

여러 클래스에 공통적으로 사용될 수 있는 추상 클래스를 바로 작성하거나 기존 클래스의 공통 부분을 뽑아서 추상 클래스를 만든다

추상화 vs 구체화

// 1.
GregorianCalendar cal = new GregorianCalendar(); // 구체적
// 2.
Calendar cal = Calendar.getInstance(); // 추상적

1번의 코드는 구체적으로 어떤 클래스를 사용할 지 어떤 타입인 지 구체적으로 명시되어 있다
GregorianCalendar는 서양력을 따르는 달력으로 다른 달력을 사용하려면 새로 정의해야 한다

2번의 코드의 Calendar추상 클래스로 자손 객체를 반환하며 getInstance()가 뭘 반환할 지 모호하게 작성되어 있어 태국력이나 서양력, 그외의 달력으로 새로 정의할 필요없이 반환받을 수 있다

profile
할 수 있다

0개의 댓글