본문 바로가기
Study/JAVA

[Java] 7-7. 인터페이스(interface)

by jeongwle 2022. 9. 6.
728x90
반응형

 

인터페이스(interface)

1. 인터페이스란?

인터페이스는 일종의 추상 클래스이다. 인터페이스는 추상 메서드를 갖지만 추상 클래스보다 추상화 정도가 높아서 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상 메서드와 상수만을 멤버로 가질 수 있다. 인터페이스 또한 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.

 

2. 인터페이스의 작성

인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 선언부에서 class 키워드 대신 interface 키워드를 사용한다. 그리고 인터페이스 또한 접근 제어자로 public 또는 default를 사용할 수 있다.

interface 인터페이스 이름{
  public static final 타입 상수이름 = 값;
  public abstract 메서드이름(매개변수 목록);
}​

일반적인 클래스의 멤버들과 달리 인터페이스의 멤버들은 다음과 같은 제약사항이 있다.

모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다.
모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
* 단, static메서드와 디폴트 메서드는 예외(JDK1.8부터)​

인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이므로 제어자를 생략할 수 있는 것이며, 편의상 생략하는 경우가 많다. 원래는 인터페이스의 모든 메서드는 추상메서드이어야 하는데 JDK1.8부터 인터페이스에 static 메서드와 디폴트 메서드(default method)의 추가를 허용하도록 변경되었다.

 

3. 인터페이스의 상속

인터페이스는 인터페이스로부터만 상속 받을 수 있고 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. 클래스의 상속과 마찬가지로 하위 인터페이스는 상위 인터페이스에 정의된 멤버 모두를 상속받는다.

interface Movable {
  void move(int x, int y);
}

interface Attackable {
  void attack(Unit u);
}

interface Fightable extends Movable, Attackable { }​

 

4. 인터페이스의 구현

인터페이스도 추상 클래스 처럼 그 자체로 인스턴스를 생성할 수 없다. 추상 클래스가 상속을 통해 추상 메서드를 완성하는 것처럼, 인터페이스도 추상 메서드의 몸통을 만들어주는 클래스를 작성해야 한다. 대신 extends라는 키워드 대신 implements라는 키워드를 사용한다.

class 클래스이름 implements 인터페이스 이름 {
  // 추상 메서드 구현
}​

만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면 abstract 키워드를 붙여 추상 클래스로 선언해야 한다.

 

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

상속부분에서 말했듯이 다중상속은 장점도 있지만 단점이 더 크다 판단하여 자바에서는 다중 상속을 허용하지 않는다. 그러나 다른 객체지향언어인 C++에서 다중상속을 허용하여 자바는 다중 상속을 허용하지 않는다는 것이 단점으로 부각되는 것에 대한 대응으로 자바도 인터페이스를 이용하면 다중상속이 가능하다 라고 하는 것이다. 자바에서 인터페이스로 다중 상속을 구현하는 경우는 거의 없다. 인터페이스를 이용한 다중상속에 대한 내용은 가볍게 맛만 보고 넘어가라고 책에서 말하고 있다. 그런데 그냥 맛도 안보고 넘어가야겠다.

 

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

인터페이스 역시 이를 구현한 클래스의 상위라 할 수 있다. 그래서 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있고 인터페이스 타입으로의 형변환도 가능하다.

인터페이스 Fightable을 클래스 Fighter가 구현했을 때, 다음과 같이 Fighter인스턴스를 Fightable타입의 참조변수로 참조하는 것이 가능하다.

Fightable f = (Fightable)new Fighter();
  or
Fightable f = new Fighter();​

따라서 인터페이스는 메서드의 매개변수의 타입으로도 사용될 수 있다. 인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다. 그래서 메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것도 가능하다. 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다. 이 문장은 외워야 한다고 한다.

 

7. 인터페이스의 장점

인터페이스를 사용하는 이유와 그 장점을 정리해보자.

1. 개발시간을 단축시킬 수 있다.
2. 표준화가 가능하다.
3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
4. 독립적인 프로그래밍이 가능하다.​

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

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

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

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

 

8. 인터페이스의 이해


클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
메서드를 사용하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.​

class A {
  public void methodA(B b) {
    b.methodB();
  }
}

class B {
  public void methodB() {
    System.out.println("methodB()");
  }
}

class InterfaceTest {
  public static void main(String args[]) {
    A a = new A();
    a.methodA(new B()); // methodB()
  }
}​

위의 코드와 같은 경우 클래스 A와 클래스 B는 서로 직접적인 관계에 있다. 클래스 A를 작성하려면 클래스 B가 이미 작성되어 있어야 하고, 만일 클래스 B의 methodB 선언부가 변경되면 이를 사용하는 클래스 A도 변경되어야 한다.  하지만 클래스 A가 클래스 B를 직접 호출하지 않고 인터페이스를 매개체로 해서 클래스 A가 인터페이스를 통해 클래스 B의 메서드에 접근하도록 하면, 클래스 B에 변경사항이 생기거나 같은 기능의 다른 클래스로 대체 되어도 클래스 A는 영향을 받지 않는다.

interface I {
  public abstract void methodB();
}

class A {
  public void methodA(I i) {
    i.methodB();
  }
}

class B implements I {
  public void methodB() {
    System.out.println("methodB()");
  }
}

class InterfaceTest {
  public static void main(String args[]) {
    A a = new A();
    a.methodA(new B()); // methodB()
  }
}​​

클래스 A를 작성하는데 클래스 B가 사용되지 않았다. 클래스 A와 클래스 B는 직접적인 관계에서 간접적인 관계로 바뀐 것이다. 클래스 A는 인터페이스 I하고만 직접적인 관계에 있어서 클래스 B의 변경에 영향을 받지 않는다.

 

9. 디폴트 메서드와 static 메서드

인터페이스에 추상 메서드만 선언할 수 있는데, JDK1.8부터 디폴트 메서드와 static메서드도 추가할 수 있게 되었다. static메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 추가하지 못할 이유가 없다. 인터페이스의 static메서드 역시 접근 제어자가 항상 public이며, 생략할 수 있다.

디폴트 메서드
인터페이스에 메서드를 추가한다는 것은 추상 메서드를 추가한다는 의미이다. 그래서 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야한다. 인터페이스의 변경이 일어나지 않으면 좋겠지만 설계를 잘해도 언젠가 변경은 발생할 수 있다. 그래서 JDK의 설계자들은 고심 끝에 디폴트 메서드(default method)라는 것을 고안해 내었다. 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

디폴트 메서드는 앞에 default 키워드를 붙이며 추상 메서드와 달리 몸통{}이 있어야 한다. 디폴트 메서드 역시 접근 제어자가 public이고 생략 가능하다.

interface MyInterface {
  void method();
  void newMethod(); // 추상 메서드
}

// 디폴트 메서드를 이용하면

interface MyInterface {
  void method();
  default void newMethod() {}
}​

새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 생길 수 있다. 이 충돌을 해결하는 규칙은 다음과 같다.

1. 여러 인터페이스의 디폴트 메서드 간의 충돌
  - 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
  
2. 디폴트 메서드와 상위 클래스의 메서드 간의 충돌
  - 상위 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.​

위의 규칙이 외우기 귀찮다면 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 해버리면 그만이라고 한다.
728x90
반응형

댓글