interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(매개변수목록);
}
interface Movable {
/** 지정된 위치(x, y)로 이동하는 기능의 메서드 **/
void move(int x, int y);
}
interface Attackable {
/** 지정된 대상(u)을 공격하는 기능의 메서드 **/
void attack(Unit u);
}
interface Fightable extends **Moveable**, **Attackable**{}
// 자손 인터페이스(Fightable)는 조상 인터페이스(Movable, Attackable)에 정의된 멤버 모두 상속
// 그래서 Fightable에는 정의된 멤버가 없지만, 상속받은 move,attack을 멤버로 갖게 된다.
인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데, 키워드 'implements'를 사용.
예제 7-24/FighterTest.java
class FighterTest {
public static void main(String[] args) {
Fighter f = new Fighter();
if (f instanceof Unit) {
System.out.println("f는 Unit클래스의 자손입니다.");
}
if (f instanceof Fightable) {
System.out.println("f는 Fightable인터페이스를 구현했습니다.");
}
if (f instanceof Movable) {
System.out.println("f는 Movable인터페이스를 구현했습니다.");
}
if (f instanceof Attackable) {
System.out.println("f는 Attackable인터페이스를 구현했습니다.");
}
if (f instanceof Object) {
System.out.println("f는 Object클래스의 자손입니다. ");
}
}
}
class Fighter extends Unit implements Fightable {
**public void move(int x, int y)** { /* 내용생략 */ }
public void attack(Unit u) { /* 내용생략 */ }
}
class Unit {
int currentHP;
int x;
int y;
}
interface Fightable extends Movable, Attackable { }
interface Movable { **void move(int x, int y);** }
interface Attackable { void attack(Unit u); }
만일 두 개의 클래스로부터 상속을 받아야 할 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부의 멤버로 포함시키는 방식으로 처리하거나 어느 한쪽의 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.
public class Tv {
protected boolean power;
protected int channel;
protected int volume;
public void power() { power =! power; }
public void channelUp() { channel++; }
public void channelDown() { channel--; }
public void volumeUp() { volume++; }
public void volumeDown() { volume--; }
}
public class VCR {
protected int counter; // VCR의 카운터
public void play() {
// Tape을 재생한다.
}
public void stop() {
// Tape을 멈춘다.
}
public void reset() {
counter = 0;
}
public int getCounter() {
return counter;
}
public void setCounter(int c) {
counter = c;
}
}
/* VCR클래스에 정의된 메서드와 일치하는 추상메서드를 갖는 인터페이스를 작성한다. */
public interface IVCR {
public void play();
public void stop();
public void reset();
public int getCounter();
public void setCounter(int c);
}
/* 이제 IVCR 인터페이스를 구현하고 Tv클래스로부터 상속받는 TVCR클래스를 작성한다.
이때 VCR클래스 타입의 참조변수를 멤버변수로 선언하여
IVCR인터페이스의 추상메서드를 구현하는데 사용한다. */
public class TVCR extends Tv implements IVCR {
VCR vcr = new VCR();
public void play() {
vcr.play(); //코드를 작성하는 대신 VCR인스턴스의 메서드를 호출한다.
}
public void stop() {
vcr.stop();
}
public void reset() {
vcr.reset();
}
public int getCounter() {
return vcr.getCounter();
}
public void setCounter(int c) {
vcr.setCounter(c);
}
Fightable f = (Fightable)new Fighter();
또는
Fightable f = new Fighter();
//따라서 인터페이스는 메서드의 매개변수 타입으로 사용 가능
Fightable method() {
...
Fighter f = new Fighter();
return f;
}
//리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한
//클래스의 인스턴스를 반환한다는 것을 의미한다.
interface Parseable {
//구문 분석작업을 수행한다.
public abstract void parse(String fileName);
}
/*구문분석을 수행하는 기능을 구현할 목적으로 추상메서드 'parse(String fileName)'을 정의했다.*/
class ParserManager {
//리턴타입이 Parseable인터페이스이다.
public static Parseable getParser(String type) {
if(type.equals("XML")) {
return new XMLParser();
} else {
Parseable p = new HTMLParser();
return p;
// return new HTMLParser();
}
}
}
/* getParser메서드는 매개변수로 넘겨받는 type의 값에 따라 XMLParse인스턴스
또는 HTMLParser인스턴스를 반환한다.*/
/* getParser메서드의 수행결과로 참조변수 parser는 XMLParser인스턴스의 주소값을 갖게 된다
마치 'Parseable parser = new XMLParser();'이 수행된 것과 같다.
class XMLParser implements Parseable {
public void parse(String fileName) {
/* 구문 분석작업을 수행하는 코드를 적는다.*/
System.out.println(fileName + "- XML parsing completed.");
}
}
class HTMLParser implements Parseable {
public void parse(String fileName) {
/* 구문 분석작업을 수행하는 코드를 적는다.*/
System.out.println(fileName + "-HTML parsing completed.");
}
}
class ParserTest {
public static void main(String args[]) {
Parseable parser = ParserManager.getParser("XML");
parser.parse("document.xml");
parser = ParserManager.getParser("HTML");
parser.parse("document2.html");
}
}
/* getParser메서드의 수행결과로 참조변수 parser는
→ 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다.
메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다.
→ 기본틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하도록 함으로써 일관되고 정형화된 프로그램의 개발이 가능.
→ 아무 관계없는 클래스들에게 하나의 인터페이스를 공통적으로 구현함으로써 관계를 맺어 줄 수 있다.
→ 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다.
클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해 간접적인 관계로 변경하면, 한 클래스의 변경이 다른 클래스에 영향을 미치지 않는 프로그래밍이 가능하다.
class RepairableTest{
public static void main(String[] args) {
Tank tank = new Tank();
Dropship dropship = new Dropship();
Marine marine = new Marine();
SCV scv = new SCV();
scv.repair(tank); //SCV가 Tank를 수리하도록 한다.
scv.repair(dropship);
// scv.repair(marine); //매린은 GroundUtnit이지만, Repairable인터페이스를 작성하지 않아서 수리 불가
}
}
interface Repairable {}
class GroundUnit extends Unit2 {
GroundUnit(int hp) {
super(hp);
}
}
class AirUnit extends Unit2 {
AirUnit(int hp) {
super(hp);
}
}
class Unit2 {
int hitPoint;
final int MAX_HP;
Unit2(int hp) {
MAX_HP = hp;
}
//...
}
class Tank extends GroundUnit implements Repairable {
Tank() {
super(150); //탕크의 피는 150
hitPoint = MAX_HP;
}
public String toString() {
return "Tank";
}
//...
}
class Dropship extends AirUnit implements Repairable {
Dropship() {
super(125); //돱십 피는 125
hitPoint = MAX_HP;
}
public String toString() {
return "Dropship";
}
//...
}
class Marine extends GroundUnit {
Marine() {
super(40);
hitPoint = MAX_HP;
}
//...
}
class SCV extends GroundUnit implements Repairable{
SCV() {
super(60);
hitPoint = MAX_HP;
}
/*repair메서드의 매개변수 r은 Repariable타입이기 때문에 인터페이스 Reapairable에 정의된
멤버만 사용할 수 있다. 그래서 instanceof연산자로 타입을 체크한 뒤 캐스팅하여 Unit클래스에 정의된
hitPoint와 MAX_HP를 사용할 수 있도록 하였다.*/
void repair(Repairable r) {
if (r instanceof Unit2) {
Unit2 u = (Unit2)r;
while(u.hitPoint!=u.MAX_HP) {
//유닛의 피를 채움
u.hitPoint++;
}
System.out.println( u.toString() + "의 수리가 끝났습니다.");
}
}
//...
}
인터페이스의 이해를 위해서는 아래 두 가지 사항을 반드시 염두해야함.
클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 됨)
- 예제 7-29/InterfaceTest3.java
class InterfaceTest {
public static void main(String[] args) {
A a = new A();
a.methodA();
}
}
class A {
void methodA() {
I i = InstanceManager.getInstance();
i.methodB();
System.out.println(i.toString()); //i로 Object클래스의 메서드 호출가능
}
}
interface I {
public abstract void methodB();
}
class B implements I {
public void methodB() {
System.out.println("methodB in B class");
}
public String toString() { return "class B";}
}
class InstanceManager {
public static I getInstance() {
return new B(); //다른 인스턴스로 바꾸려면 여기만 변경하면 됨.
}
}
원래는 인터페스에 추상 메서드만 선언할 수 있는데 , JDK1.8부터 디폴트 메서드와 static메서드도 추가할 수 있게 되었다.
여러 인터페이스의 디폴트 메서드간의 충돌
→ 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
디폴트 메서드와 조상 클래스의 메서드간의 충돌
→ 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.