여러 가지 형태를 가질 수 있는 능력
조상 타입 참조변수로 자손 타입 객체를 다루는 것
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() {} // 인스턴스 메서드
}
디폴트 메서드가 기존의 메서드와 충돌할 때 해결책
-> 충돌했을때, 그냥 직접 오버라이딩 하면 해결된다.