[java] 인터페이스(interface)

인터페이스(interface)

- 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

- 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 기본 설계도라 할 수 있다. 인터페이스도 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.

 

인터페이스의 작성

- 문법

interface 인터페이스명{

public static final 타입 상수이름 = 값;

public abstract 메서드이름(매개변수 목록);

}

접근제어자로 public, default를 사용할 수 있다.

- 제약사항

모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다.

모든 메서드는 public abstract이어야 하며, 이를 생략할 수 있다.

[참고] 인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자가 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.

 

인터페이스의 상속

- 인터페이스는 인터페이스로부터만 상속(extends)받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. 클래스와 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.

 

인터페이스의 구현

- 인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야하는데, 그 방법은 추상클래스와 다르지 않다. 다만, 클래스는 확장한다는 의미의 'extends'를 사용하지만 인터페이스는 구현한다는 의미의 'implements'를 사용한다.

- 만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, 추상클래스로 선언되어야 한다.

- 인터페이스는 상속 대신 구현이라는 용어를 사용하지만, 인터페이스로부터 상속받은 추상메서드를 구현하는 것이기 때문에 인터페이스도 조금은 다른 의미의 조상이라고 할 수 있다.

 

인터페이스를 이용한 다중상속

- 두 조상으로부터 상속받을때 두 조상의 멤버들이 동일하여 어느 조상의 멤버를 상속받게되는 것인지 알 수 없게 될때, 어느 한 쪽으로부터 상속을 포기하던가, 이름이 충돌하지 않도록 조상클래스를 변경하는 수 밖에 없다. 이러한 이유로 자바에서는 다중상속을 허용하지 않는데 C++에서는 다중상속을 허용하기 때문에 이에 대한 대응으로 인터페이스를 사용하여 다중상속이 가능하게 하였다. 그러나 실제로 다중상속만을 위해서 인터페이스를 사용하지는 않는다.

 

인터페이스를 이용한 다형성

- 인터페이스도 다형성이 가능하다. 인터페이스를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.

- 인터페이스는 메서드의 매개변수의 타입으로 사용될 수 있다. 즉 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다.

--------------------------------------------------------------------------
인터페이스를 이용한 다형성 포괄 예제

interface Fruits{}

 

class Apple implements Fruits{}

class Strawberry implements Fruits{}

class Orange implements Fruits{}

 

class Watermelon{}  // Fruits implements 하지 않은 클래스!

 

// 매개변수와 리턴타입으로도 가능하다는걸 보여주는 Deal 클래스이다.

class Deal{

Fruits fruits;  // 상속 복습. has a 관계.

 

Deal(){}  // 기본 생성자

 

Deal(Fruits fruits){

this.fruits = fruits;

}

 

void buy(Fruits f){  // 매개변수로 인터페이스 사용

System.out.println(f+"를 샀습니다.");

}

 

Fruits getFruits(){  // 리턴타입으로 지정하는 것도 가능하다.

return fruits;

}

}

 

public class Test {

public static void main(String[] args) {

/*

* Apple, Strawberry, Orange는 Fruits를 상속받은 클래스이기 때문에 이 클래스들의 인스턴스들은 Fruits 타입의 참조변수에 대입될 수 있다.

* 자식 클래스의 인스턴스는 별도의 캐스팅 없이 프로모션 된다.

*/

Fruits fruits1 = new Apple();

Fruits fruits2 = new Strawberry();

Fruits fruits3 = new Orange();  // promotion

 

Deal dealer = new Deal();

dealer.buy(fruits1);  // Fruits타입의 fruits1은 매개변수로 가능하다.

 

Strawberry strawberry = (Strawberry)fruits2;  // casting

dealer.buy(strawberry);  // Strawberry타입의 strawberry도 매개변수로 가능하다. 결국은 이것도 promotion

 

Watermelon watermelon = new Watermelon();

/*

* Watermelon은 Fruits를 구현하지 않았기 때문에 Fruits 속성을 가지고 있지 않다.

* 따라서 메서드의 파라미터 값에 해당하지 않으므로 에러가 난다.

*/

dealer.buy(watermelon);  // error msg : The method buy(Fruits) in the type Deal is not applicable for the arguments (Watermelon)

 

 

Deal dealer2 = new Deal(fruits3);

System.out.println(dealer2.getFruits());

}

}

--------------------------------------------------------------------------

- 만약 인터페이스의 다형성이 없다면 위의 buy(Fruits)메서드는 각 과일의 타입마다 오버로딩 해주어야 할것이다.(buy(Apple), buy(Strawberry), buy(Orange) 등...) 하지만 인터페이스의 다형성으로 인해 프로그래밍의 중복을 줄여주고 사용자의 사용도 간단해졌다.

--------------------------------------------------------------------------
인터페이스 정리 및 상속계층도

interface Unit{}

interface Fightable{}

 

class GroundUnit implements Unit{}  // 인터페이스는 implements를 사용하여 구현한다.

class AirUnit implements Unit{}

 

class Marin extends GroundUnit implements Fightable{}  // 상속과 구현이 동시에 가능하다.

class firebat implements Unit, Fightable{}  // 다중상속이 가능하다.

 

class overload extends AirUnit{}

 

추상화 <-> 구체화

오른쪽에 위치한 클래스들의 인스턴스를 왼쪽에 위치한 클래스의 타입으로 참조하는것은 가능하다. 하지만 그 반대는 불가능하다.

- Unit

- Fightable

 

- Unit - GroundUnit  // GroundUnit은 GroundUnit와 Unit타입 두가지를 가진다.

- Unit - AirUnit

 

- Unit - GroundUnit - Marin  // Marin은 총 세가지의 타입을 가진다.

   Fightable ---------┘

 

Unit - firebat

   Fightable ┘

 

 

Unit - AirUnit - overload

 
예제
Unit u1 = new Marin();  // 오른쪽에 위치한 클래스들의 인스턴스를 왼쪽에 위치한 클래스의 타입으로 참조하는것은 가능
Unit u2 = new firebat();
Unit u3 = new overload();
 
/* 조상 타입의 인스턴스를 자식 클래스 타입에 대입할 수 없다. */
overload ol1 = new AirUnit();  // ERROR msg : Type mismatch: cannot convert from AirUnit to overload
/* u3가 참조하고 있는 인스턴스는 overload지만 참조변수의 타입은 Unit이다.
overload ol2 = u3;  // ERROR msg : Type mismatch: cannot convert from Unit to overload
 
overload ol3 = (overload)u3;  // casting 후 가능해진다.
--------------------------------------------------------------------------
 

인터페이스의 장점

- 개발시간을 단축시킬 수 있다.

인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다. 그리고 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하도록 하여, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.

- 표준화가 가능하다.

프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.

- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.

서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.

- 독립적인 프로그래밍이 가능하다.

클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다. 

[참고] 자세한 설명은 여기(새창| 강의필기 0903일 장점부분) 참고

 

인터페이스의 이해

- 클래스를 사용하는 쪽(user)과 클래스를 제공하는 쪽(provider)이 있다.

- 메서드를 사용(호출)하는 쪽(user)에서는 사용하려는 메서드(provider)의 선언부만 알면 된다.(내용은 몰라도 된다.)

[참고] 자세한 설명은 여기(새창| 강의필기 0903일 간접적인 관계)참고