여러 가지 형태를 가질 수 있는 능력
조상 타입 참조변수로 자손 타입 객체를 다루는 것
class Tv {
	boolean power;
    int channel;
    
    void power() { power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}
class SmartTv extends Tv{
	String text;
    void caption() { /*내용 생략*/ }
}
Tv t = new SmartTv();
참조변수의 타입(조상 - Tv)과 객체의 타입(자손 - SmartTv)이 불일치
객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이
1. SmartTv s = new SmartTv(); 타입 일치

Tv t = new SmartTv(); 타입 불일치
자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 없다.
SmartTv s = new Tv(); 에러, 사용 불가
Q. 참조변수의 타입은 인스턴스의 타입과 반드시 일치해야 하는지?
A. 아니요. 일치하는 것이 보통이지만 일치하지 않을 수도 있습니다.
Q. 참조변수가 조상타입일 때와 자손타입일 때의 차이는?
A. 참조변수로 사용할 수 있는 멤버의 갯수가 달라집니다.
Q. 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 있나요?
A. 아니요. 허용되지 않습니다.
사용할 수 있는 멤버의 개수를 조절하는 것
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!!!");
    }
}
class Ex7_7 {
    public static void main(String[] args) {
        FireEngine fe = new FireEngine();
        Car car = null;
        FireEngine fe2 = null;
        fe.water();
        car = (Car) fe;
        car.water(); // 에러
        fe2 = (FireEngine) car;
        fe2.water();
    }
}

class Ex7_7 {
    public static void main(String[] args) {
        Car car = null;
        FireEngine fe = null;
        
        FireEngine fe2 = (FireEngine) car;
        Car car2 = (Car) fe2;
        car2.drive(); // NullPointerException 발생
    }
}
class Ex7_7 {
    public static void main(String[] args) {
        Car c = new Car();
        FireEngine fe3 = (FireEngine) c; // 형변환 실행 에러 java.lang.ClassCastException 발생
        fe3.water(); // 컴파일ok
    }
}
컴파일은 되지만 형변환 실행 에러 java.lang.ClassCastException 발생
참조변수 fe3가 가리키는 실제 인스턴스는 Car 인스턴스이다.
Car 인스턴스에는 water() 메서드가 없기 때문에 에러가 발생
참조변수가 가리키는 실제 인스턴스가 무엇인지 꼭 확인하고 그 인스턴스의 멤버 개수를 넘어서면 안된다.
참조변수의 형변환 가능여부 확인에 사용, 가능하면 true 반환
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
Q. 참조변수의 형변환은 왜 하나요?
A. 참조변수를 변경함으로써 사용할 수 있는 멤버의 개수를 조절하기 위해서
Q. instanceof 연산자는 언제 사용하나요?
A. 참조변수를 형변환하기 전에 형변환 가능 여부를 확인할 때
참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.
class Product {
    int price;
    int bonusPoint;
    Product(int price) {
        this.price = price;
        bonusPoint = (int) (price / 10.0);
    }
}
class Tv1 extends Product {
    Tv1() {
        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) {
        if (money < p.price) {
            System.out.println("잔액이 부족합니다.");
            return;
        }
        money -= p.price;
        bonusPoint += p.bonusPoint;
        System.out.println(p + "을/를 구입하셨습니다.");
    }
}
class Ex7_8 {
    public static void main(String[] args) {
        Buyer b = new Buyer();
        b.buy(new Tv1());
        b.buy(new Computer());
        System.out.println("남은 잔액은 " + b.money + "만원 입니다.");
        System.out.println("현재 보너스포인트는 " + b.bonusPoint + "점 입니다.");
    }
}
class Tv1 extends Product {
    Tv1() {
        super(100);
    }
    public String toString() {
        return "Tv";
    }
}
Product p = new Tv1();
b.buy(p);
b.buy(new Tv1()); 가 된다.조상타입의 배열에 자손들의 객체를 담을 수 있다.
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

class Buyer {
    int money = 1000;
    int bonusPoint = 0;
    int i = 0;
    
    Product[] cart = new Product[10];
    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 + "을/를 구입하셨습니다.");
    }
}
Product[] cart = new Product[10]; : 구입한 물건을 담을 배열 생성cart[i++] = p; : 구입한 물건 p를 cart[0] 부터 차례대로 저장	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 + "입니다.");
	}
}
if(cart[i]==null) break; cart[] 배열에 아무것도 없다면 break 문으로 빠져나온다.sum += cart[i].price; 구입한 제품들의 가격을 누적해 sum에 저장itemList += cart[i] + ", "; cart[i] = cart[i].toString 구입한 제품들을 출력미완성 설계도, 미완성 메서드를 갖고 있는 클래스
abstract class Player {
	abstract void play(int pos);
    abstract void stop();
}
다른 클래스 작성에 도움을 주기 위한 것, 인스턴스 생성 불가
Player p = new Player(); 에러, 추상 클래스는 인스턴스 생성 불가상속을 통해 추상 메서드를 완성해야 인스턴스 생성 가능
class AudioPlayer extends Player {
	void play(int pos) { /*내용 생략*/ }
    void stop() { /*내용 생략*/ }
}
AudioPlayer ap = new AudioPlayer(); 
Player p = new AudioPlayer();
Player p = new AudioPlayer(); : 다형성 때문에 조상 타입인 Player 참조변수 p로도 인스턴스 생성 가능abstract class AudioPlayer extends Player {
	void play(int pos) { /*내용 생략*/ }
}
미완성 메서드, 구현부(몸통 {})가 없는 메서드
abstract 리턴타입 메서드이름();꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우에 작성
추상 메서드 호출가능(호출할 때는 선언부만 필요)
abstract class Player {
	boolean pause; // iv
    int currentPos;
    
    Player() { // 생성자
    	pause = false;
        currentPos = 0;
    }
    
    abstract void play(int pos);
    abstract void stop();
    
    void play() { // 인스턴스 메서드
    	play(currentPos);
    }
}    
abstract void play(int pos); : "지정된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성되어야 한다." 라고 자손 클래스들에게 필수적으로 구현을 강제하기 위해서 추상 메서드로 작성
void play() { play(currentPos); } 나중에 상속을 통해 자손이 play(int pos) 메서드를 구현하면 호출 가능
여러 클래스에 공통적으로 사용될 수 있는 추상 클래스를 바로 작성하거나 기존 클래스의 공통 부분을 뽑아서 추상 클래스로 만든다.
public class Ex7_10 {
	public static void main(String[] args) {
		Unit[] group = { new Marine(), new Tank(), new Dropship() };
		for (int i = 0; i < group.length; i++)
			group[i].move(100, 200);
	}
}
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) {
		System.out.println("Marine[x=" + x + ",y=" + y + "]");
	}
	void stimPack() { /* 스팀팩을 사용한다. */ }
}
class Tank extends Unit { // 탱크
	void move(int x, int y) {
		System.out.println("Tank[x=" + x + ",y=" + y + "]");
	}
	void changeMode() { /* 시즈모드로 변환한다. */ }
}
class Dropship extends Unit { // 수송선
	void move(int x, int y) {
		System.out.println("Dropship[x=" + x + ",y=" + y + "]");
	}
	void load()   { /* 유닛들을 태운다. */ }
	void unload() { /* 유닛들을 내린다. */ }
}
abstract class Unit { int x, y; abstract void move(int x, int y); void stop() { /* 현재 위치에 정지 */ } }
- 마린, 탱크, 드랍쉽이 이동하는 방식(구현)은 모두 다르다. 그러나 이동한다는 메서드는 필수적으로 들어가야 되기 때문에 Unit 조상 클래스에 abstract void move(int x, int y) 라는 추상 메서드를 만들어주는 것이다.
 
public class Ex7_10 { public static void main(String[] args) { Unit[] group = { new Marine(), new Tank(), new Dropship() }; for (int i = 0; i < group.length; i++) group[i].move(100, 200); } }
Unit[] group = { new Marine(), new Tank(), new Dropship() };다형성의 장점 2번째인 조상타입(Unit)의 배열에 자손 객체들을 담을 수 있다. 마린, 탱크, 드랍쉽을 모두 (100, 200) 좌표로 이동시키는 for 문for (int i = 0; i < group.length; i++) group[i].move(100, 200); }
단계별 추상 클래스의 작성

추상화된 코드는 구체화된 코드보다 유연하다. 변경에 유리
추상 메서드의 집합
구현된 것이 전혀 없는 설계도.껍데기(모든 멤버가 public)
interface 인터페이스이름 {
	public static final 타입 상수이름 = 값;
    public abstract 메서드이름(매개변수목록);
}
인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상이 아님)
다중 상속이 가능(추상 메서드는 구현부가 없어서 충돌해도 문제가 없기 때문에)
인터페이스에 정의된 추상 메서드를 완성하는 것
class 클래스이름 implements 인터페이스이름 {
	// 인터페이스에 정의된 추상 메서드를 구현
}
Q. 인터페이스란?
A. 추상 메서드의 집합( + 상수, static 메서드, 디폴트 메서드)
Q. 인터페이스의 구현이란?
A. 인터페이스의 추상 메서드 몸통 {} 만드는 것
Q. 추상 클래스와 인터페이스의 공통점은?
A. 추상 메서드를 가지고 있다.
Q. 추상 클래스와 인터페이스의 차이점은?
A. 인터페이스는 iv, im, 생성자를 가질 수 없다.
인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 인스턴스만 가능
인터페이스를 메서드의 반환타입으로 지정할수 있다.
Fightable method() {
	Fighter f = new Fighter();
    return (Fightable) f;
}
class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { /*내용 생략*/ }
    public void attack(Fightable f) { /*내용 생략*/ }
}
...
Fightable f = method(); // 호출
public void move(int x, int y) { /*내용 생략*/ }
오버라이딩 규칙 : 조상(public)보다 접근 제어자의 범위가 좁으면 안된다.
반환타입이 인터페이스인 메서드는 인터페이스를 구현한 클래스의 인스턴스를 반환한다.
메서드를 호출해서 저장할 값의 타입은 인터페이스 타입이 일치 혹은 형변환이 가능한 타입이어야 한다.
두 객체 간의 연결을 돕는 중간 역할
선언(설계)과 구현을 분리시킬 수 있게 한다.
class B {
	public void method() {
    	System.out.println("클래스 B의 메서드");
    }
}
interface I {
	public void method();
}
class B implements I {
	public void method() {
    	System.out.println("클래스 B의 메서드");
    }
}
직접적인 관계의 두 클래스 A-B
class A {
    public void method(B b) {
        b.method();
    }
}
class B {
    public void method() {
        System.out.println("클래스 B의 메서드");
    }
}
public class InterfaceTest {
    public static void main(String[] args) {
        A a = new A();
        a.method(new B()); // A가 B를 사용(A가 B에 의존)
    }
}
a.method(new B()); A가 B를 사용(A가 B에 의존)
B를 C로 변경하면 클래스 A의 코드도 변경해주어야 한다.
-> public void method(C c) { c.method(); }
간접적인 관계의 두 클래스 A-I-B
-> 변경에 유리한 유연한 설계가 가능
class A {
    public void method(I i) {
        i.method();
    }
}
interface I { // 인터페이스로 선언부를 분리
    public void method();
}
class B implements I {
    public void method() {
        System.out.println("클래스 B의 메서드");
    }
}
class C implements I {
    public void method() {
        System.out.println("클래스 C의 메서드");
    }
}
public class InterfaceTest {
    public static void main(String[] args) {
        A a = new A();
        a.method(new B());
    }
}
class A { public void method(I i) { i.method(); } } 인터페이스 I를 구현한 클래스의 인스턴스만 매개변수로 가능
B를 C로 변경해도 클래스 A를 수정해줄 필요가 없다.
-> main 메서드에서 a.method(new C()); 한번 변경해주면 끝이기 때문에 코드가 유연하고 변경이 유리하다.
개발 시간을 단축할 수 있다.

표준화가 가능하다.

서로 관계가 없는 클래스들을 관계 맺어줄 수 있다.

SCV, 탱크, 드랍쉽을 수리하는 메서드를 작성하려고 한다.
그런데 저 셋을 묶을 마땅한 관계가 보이지 않을때 인터페이스를 사용한다.

interface Repairable() {}
class SCV extends GroundUnit implements Repairable { //.. }
class Tank extends GroundUnit implements Repairable { //.. }
class Dropship extends AirUnit implements Repairable { //.. }
void repair(Repairable r) {
	if(r instanceof Unit) {
    	Unit u = (Unit) r;
        while(u.hitPoint != u.MAX_HP) {
        	u.hitPoint++; // Unit의 HP를 증가
        }
    }
}
인터페이스에 디폴트 메서드, static 메서드를 추가 가능(JDK 1.8부터)
인터페이스에 새로운 메서드(추상 메서드)를 추가하기 어렵다.
-> 추상 메서드를 추가하게 되면 기존에 인터페이스를 구현했던 클래스들 전부가 새로운 추상 메서드를 추가로 구현해야 한다.
해결책 : 디폴트 메서드(default mathod)
interface MyInterface {
	void method();
    default void newMethod() {} // 인스턴스 메서드
}
디폴트 메서드가 기존의 메서드와 충돌할 때 해결책
-> 충돌했을때, 그냥 직접 오버라이딩 하면 해결된다.