Web/Java

[Java] 인터페이스

학습할 것

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9

 

 

 

 

 

인터페이스의 정의

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

 

인터페이스 멤버의 제약사항

- 모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
단, static 메서드와 디폴트 메서드는 예외

 

 

예시)

인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 편의상 생략하는 경우가 많습니다.

아래 예시와 같이 생략된 제어자들은 컴파일 시에 컴파일러가 자동적으로 추가해줍니다.

package javastudy.ch6;

public interface InterfaceEx {
    public static final int SPADE = 4;
    final int DIAMOND = 3; // public static final int DIAMOND = 3;
    static int HEART = 2; // public static final int HEART = 2;
    int CLOVER = 1; // public static final int CLOVER = 1;
    
    public abstract String getCardNumber();
    String getCardKind(); // public abstract String getCardKind();
}



 

 

인터페이스의 구현

인터페이스도 추상클래스과 같이 그 자체로는 인스턴스를 생성할 수 없습니다.

 

인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야합니다.

 

다만, 추상클래스가 상속을 받을 때 extends를 사용하지만 인터페이스는 구현한다는 의미의 implements를 사용합니다.

 

class 클래스이름 implements 인터페이스이름 {
    // 인터페이스에 정의된 추상메서드 모두를 구현해야 한다.
}

class Fighter implements Fightable {
    public void move(int x, int y) {/* 내용 생략 */}
    public void attack(Unit u) {/* 내용 생략 */}
}

 

 

 

 

인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

 

인터페이스 Phone,  구현체 Galaxy 가 있습니다.

 

 

Phone 인터페이스 타입으로 Galaxy을 사용

Phone 인터페이스 타입으로 Galaxy을 사용하면 Phone 인터페이스의 메서드만 사용가능합니다.

 

Galaxy 타입으로 Phone를 사용

Galaxy 타입으로 Phone를 사용하면 Phone의 메서드, Gaxlay의 메서드 둘 다 사용이 가능합니다.

 

 

 

Phone 인터페이스 타입으로 Galaxy의 메서드를 사용

(인터페이스 레퍼런스를 통해 구현체를 사용하는 방법)

 

Phone 인터페이스 타입으로 Galaxy의 메서드를 사용하려면 (Galaxy)으로 캐스팅하면 됩니다.

 

 

 

 

인터페이스 상속

  • 인터페이스는 인터페이스로부터만 상속받을 수 있습니다.
  • 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능합니다.

참고. 인터페이스는 클래스와 달리 Object와 같은 최고 조상이 없습니다.

 

 

 

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

interface Attackable {
    void attack(Unit u);
}

interface Fightable extends Movable, Attackable {}

 

Fightable 인터페이스가 Movable, Attackable 인터페이스 두개를 다 상속받고있습니다.

 

클래스 상속과 마찬가지로 자손 인터페이스(Fightable)은 조상 인터페이스(Movable, Attackable)에 정의된 멤버를 모두 상속받습니다.

 

그래서 Fightable자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로부터 상속받은 두 개의 추상메서드, move와 attack을 멤버로 갖게 됩니다.

 

 

 

 

인터페이스의 장점

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

 

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

일단 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는게 가능합니다.

그래서 한쪽에서는 인터페이스를 작성하고, 한쪽에서 구현클래스를 작성하면 구현될때까지 기다리지 않아도 동시에 개발을 진행할 수 있습니다.

 

2. 표준화가 가능하다.

프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 구현하게 하면 보다 정형화된 개발이 가능합니다.

 

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

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

 

이에 관해서 스타크래프트 테란의 유닛들을 예시로 이해하면 쉽습니다.

 

파란색으로 쳐진 유닛들은 SCV에 의해 repair가 가능한 유닛들입니다.

 

interface가 없다면 SCV class에서는 repair가 가능한 유닛들 별로 메소드를 따로 만들어야 할 것 입니다.

void repair(Tank t) {}
void repair(Dropship) {}
...

왜냐하면 GroundUnit와 AirUnit을 매개변수로 넣기에는 GroundUnit인 Marine은 repair가 가능하지 않기 때문입니다.

 

이 서로 상관없는 클래스들에게 관계를 맺어줄 수 있는 방법이 인터페이스 입니다. 

 

Repairable이라는 인터페이스를 만들고 repair가 가능한 유닛들이 implements하면 됩니다.

interface Repairable {}

class SCV extends GroundUnit implements Repairable {...}

class Tank extends GroundUnit implements Repairable {...}

class Dropship extends AirUnit implements Repairable {...}

 

그리고 SCV에서는 Repairable 타입을 매개변수로 받으면 됩니다.

void repair(Repairable r) {}

 

 

 

 

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

위에 3번과 같이 서로 관계없는 클래스를 관계있게 만들어도 각자의 클래스에서 구현하거나 변경한 내용은 서로 영향을 미치지 않습니다.

그래서 서로 관계가 있지만 서로 독립적인 프로그래밍이 가능하도록 만들어 줍니다.

 

 

 

 

 

 

인터페이스의 기본 메서드 (Default Method)

 

인터페이스에 새로운 메서드를 추가한다고 생각해봅시다.

 

그렇다면 인터페이스를 구현하는 모든 구현체에 새로운 메서드를 오버라이딩 해야할 것입니다.

 

아무리 설계를 잘해도 언젠가 인터페이스를 변경해야할 일이 생깁니다.

 

그래서 JDK의 설계자들은 고심끝에 디폴트 메서드 (default method)를 고안해냅니다.

(java8부터 도입)

 

 

디폴트 메서드

디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 됩니다.

 

 

디폴트 메서드 선언

디폴트 메서드는 앞에 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통{}이 있어야 합니다.

 

디폴트 메서드 역시 접근 제어자가 public이며, 생략가능합니다.

참고. 접근 제어자 default와 헷갈리면 안됩니다. public default void newMethod() {} 에서 public을 생략한 것입니다.

 

package javastudy.ch8;

public interface MyInterface {
    void method();
    default void newMethod() {}
//    public default void newMethod() {}
}

 

 

 

디폴트 메서드 사용시 주의

새로 추가된 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 생깁니다.

 

  1. 여러 인터페이스의 디폴트 메서드 간의 충돌
  2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌

 

간단한 해결방법은, 그냥 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 해버리면 됩니다.

 

 

 

 

 

인터페이스의 static 메소드

원래는 인터페이스에 추상메서드만 선언할 수 있는데, Java8부터 static 메서드도 추가할 수 있게 되었습니다.

 

사실 static 메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 인터페이스에 추가하지 못할 이유가 없었습니다.

 

인터페이스의 static 메서드 역시 접근 제어자가 항상 public이며, 생략할 수 있습니다.

 

package javastudy.ch8;

interface InterfaceStatic {
    static void myMethod() {
        System.out.println("static method");
    }
}

public class InterfaceStatcTest {
    public static void main(String[] args) {
        InterfaceStatic.myMethod();
    }
}

 

 

 

 

 

인터페이스의 private 메소드

 

Java8에서 default, static method가 추가되면서, 인터페이스 내부에 메서드를 구현할 수 있게 되었습니다.

 

위에서 언급했다시피, default, static 메서드의 기본 접근제어자는 public입니다.

그래서 외부 구현체에서 필요한 메서드가 아닌 내부에서만 작동하기를 원하는 메서드도 노출이 되는 경우가 발생하기 시작했습니다.

 

따라서, 이러한 문제점을 해결하기 위해 Java9에서는 private method를 지원하여 코드의 중복을 피하고 캡슐화를 유지할 수 있게 하였습니다.

 

 

Interface 내부에서만 쓰이는 private Method는 private으로 선언되어서 내부에서만 알 수 있습니다.

또한, 다른 인터페이스나 구현체에서 같은 이름으로 메서드를 만들 수 있습니다.

interface InterfaceStatic {

    default void setting() {
        System.out.println("return" + privateMethod());
    }

    static void myMethod() {
        System.out.println("static method");
    }

    private int privateMethod() {
        return 10;
    }

}