인터페이스란?
인터페이스는 일종의 추상클래스이다.
인터페이스는 추상클래스 처럼 추상메소드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메소드 또는 멤버변수를 구성원으로 가질 수 없다.
인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.
인터페이스의 작성
인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 interface를 사용한다는 것만 다르다.
하지만 일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항이 있다.
- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메소드는 public abstract 이어야 하며, 이를 생략할 수 있다.
인터페이스의 상속
인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.
interface Movable{
void move(int x, int y);
}
interface Attackable{
void attack(Unit u);
}
interface Fightable extends Movable, Attackable{ }
클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.
인터페이스의 구현
인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없다.
추상클래스가 상속을 통해 추상메소드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메소드의 몸통을 만들어주는 클래스를 작성해야 한다.
클래스는 확장한다는 의미의 키워드 extends를 사용하지만
인터페이스는 구현한다는 의미의 키워드 implements를 사용한다.
만일 구현하는 인터페이스의 메소드 중 일부만 구현한다면, 다음과 같이 abstract를 붙여서 추상클래스로 선언해야 한다.
abstract class Fighter implements Fightable{
public void move(int x, int y) { }
}
그리고 다음과 같이 상속과 구현을 동시에 할 수도 있다.
class Fighter extends Unit implements Fightable{
public void move(int x, int y) { }
public void attack(Unit u) { }
}
위의 Fightable, Movable, Attackable 세 개의 인터페이스가 있다고 가정해보고 다음 예시를 살펴보자.
실제로 Fighter클래스는 Unit클래스로부터 상속받고 Fightable인터페이스만을 구현했지만,
Unit클래스는 Object클래스의 자손이고, Fightable인터페이스는 Attackable과 Movable인터페이스의 자손이므로
Figther클래스는 이 모든 클래스와 인터페이스의 자손이 되는 셈이다.
Fighter클래스에서 구현할 때 메소드들의 접근제어자를 public으로 한 이유는 다음과 같다.
오버라이딩 할 때는 조상의 메소드보다 넓은 범위의 접근 제어자를 지정해야 한다.
Movable인터페이스에 void move(int x, int y)와 같이 정의되어 있지만 사실 public abstract가 생략된 것이기 때문에
이를 구현하는 Fighter클래스에서는 void move의 접근제어자를 반드시 public으로 해야 하는 것이다.
인터페이스를 이용한 다중상속
두 조상으로부터 상속받는 멤버 중에서 멤버변수의 이름이 같거나 메소드의 선언부가 일치하고 구현 내용이 다르다면 이 두 조상으로부터 상속받는 자손클래스는 어느 조상의 것을 상속받게 되는 것인지 알 수 없다.
따라서 만일 두 개의 클래스로부터 상속을 받아야 하는 상황이라면,
두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 한쪽은 클래스 내부에 멤버로 포함시키는 방식으로 처리하거나 어느 한쪽의 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.
예를 들어, Tv클래스와 VCR클래스가 있을 때, TVCR클래스를 작성하기 위해 두 클래스로부터 상속을 받을수 있으면 좋겠지만 다중상속을 허용하지 않으므로,
한 쪽만 선택하여 상속받고 나머지 한 쪽은 클래스 내에 포함시켜서 내부적으로 인스턴스를 생성해서 사용하도록 한다.
public class Tv{
protected boolean power;
protected int channel;
public void power() { } ;
public void channelUp() { };
}
public class VCR{
protected int counter;
public void play() { };
pulbic void stop() { };
}
VCR클래스에 정의된 메소드와 일치하는 추상메소드를 갖는 인터페이스를 작성한다.
public interface IVCR{
public void play() ;
public void stop() ;
}
이제 IVCR인터페이스를 구현하고 Tv클래스로부터 상속받는 TVCR클래스를 작성한다.
이때 VCR클래스 타입의 참조변수를 멤버변수로 선언하여 IVCR인터페이스의 추상메소드를 구현하는데 사용한다.
public class TVCR extends Tv implements IVCR{
VCR vcr = new VCR() ;
public void play() {
vcr.play();
}
public void stop() {
vcr.stop();
}
}
IVCR인터페이스를 구현하기 위해서는 새로 메소드를 작성해야하는 부담이 있지만 이처럼 VCR클래스의 인스턴스를 사용하면 손쉽게 다중상속을 구현할 수 있다.
또한 VCR클래스의 내용이 변경되어도 변경된 내용이 TVCR클래스에도 자동적으로 반영되는 효과를 얻을 수 있다.
사실 인터페이스를 새로 작성하지 않고도 VCR클래스를 TVCR클래스에 포함시키는 것만으로도 충분하지만,
인터페이스를 이용하면 다형적 특성을 이용할 수 있다는 장점이 있다.
인터페이스를 이용한 다형성
자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 공부했었다.
인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로
해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며,
인터페이스 타입으로의 형변환도 가능하다.
인터페이스 Fightable을 클래스 Fighter가 구현했을 때, 다음과 같이 참조할 수 있다.
Fightable f = (Fightable) new Fighter() ;
Fightable f = new Fighter() ;
따라서 인터페이스는 다음과 같이 메소드의 매개변수의 타입으로 사용될 수 있다.
void attack(Figthable f) { } ;
메소드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다.
Figthable인터페이스를 구현한 Fighter클래스가 있을 때, attack메소드의 매개변수로 Fighter인스턴스를 넘겨줄 수 있다.
즉, attack(new Fighter())와 같이 할 수 있다는 것이다.
그리고 다음과 같이 메소드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.
Figthable method() {
Fighter f = new Fighter() ;
return f;
}
리턴타입이 인터페이스라는 것은 메소드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
다음 예시를 살펴보자.
구문분석을 수행하는 기능을 구현할 목적으로 추상메소드 parse(String fileName)을 정의했다.
그리고 XMLParser와 HTMLParser클래스는 Parseable인터페이스를 구현하였다.
ParseManager클래스의 getParser메소드는 매개변수로 넘겨받는 type에 따라 각자 다른 인스턴스를 반환한다.
이 때 리턴타입이 Parseable인터페이스 이므로, 인터페이스를 구현한 클래스의 인스턴스를 반환한 것이다.
따라서 Parseable parser = ParserManger.getParser("XML"); 은
Parseable parser = new XMLParser(); 이 수행된 것과 같다.
이 때 parser를 통해 parse()를 호출하면 parser가 참조하고 있는 XMLParser인스턴스의 parse메소드가 호출된다.
이렇게 사용하는 이유는 XML구문분석기의 새로운 버젼이 나와도 컴퓨터에 설치된 프로그램을 변경하지 않고,
return new NewXMLParser(); 처럼 서버측의 변경만으로도 사용자가 새로 개정된 프로그램을 사용하는 것이 가능하기 때문이다.
'JAVA > Basic' 카테고리의 다른 글
[JAVA-basic] default 메소드와 static 메소드 (0) | 2021.09.12 |
---|---|
[JAVA-basic] 인터페이스(2) (0) | 2021.09.09 |
[JAVA-basic] 추상클래스 (0) | 2021.08.27 |
[JAVA-basic] 다형성(2) (0) | 2021.08.24 |
[JAVA-basic] 다형성 (0) | 2021.08.24 |