해당 글을 백기선 님의 자바 스터디 8주차 과제를 공부하고 공유하기 위해서 작성되었습니다.

목표

자바의 인터페이스에 대해 학습한다.

학습할 것

인터페이스 정의하는 방법

인터페이스란?

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

   추상클래스가 미완성 설계도라면, 인터페이스는 밑그림만 그려져 있는 기본 설계도라 할 수 있다.

인터페이스를 작성하는 방법

   인터페이스를 작성하는 것은 클래스를 작성하는 것과 동일하다. 단지 키워드로 class 대신 interface를 사용한다. 그리고 interface에도 클래스와 같이 접근제어자를 사용할 수 있다.

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

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

  • 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. 단, static 메서드와 default 메서드는 예외(JDK 1.8부터)

생략된 제어자는 컴파일러가 자동적으로 추가해준다.

인터페이스 구현하는 방법

   인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없다. 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데 그 방법은 추상클래스와 같다. 단, 키워드 ‘extends’가 아닌 구현한다는 의미의 ‘implements’를 사용한다.

    class 클래스이름 implements 인터페이스이름 {
        // 인터페이스에 정의된 추상메서드를 구현
    }

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

    abstract class Soldier implements Fightable {
        public void move(int x, int y) { ... }
    }

또한 상속과 구현을 동시에 할 수도 있다.

    class Soldier extends Unit implements Fightable {
        public void move(int x, int y) { ... }
        public void attack(Unit u) { ... }
    }

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

   다형성에 의해 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 사실을 알고 있다.

   인터페이스 역시 해당 인터페이스의 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.
인터페이스 타입의 매개변수가 가지는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것이다.

    interface Champion {
        void ChampName(String champName);
    }

    class LaneCheck {
        public static Champion getLane(String lane) {
            if(lane.equals("Bottom")) {
                return new Marksman();
            } else if(lane.equals("Mid")) {
                return new Nuker();
            } else {
                Champion c = new Jungle();
                return c;
            }
        }
    }

    class Marksman implements Champion {
        public void ChampName(String champName) {
            System.out.println(champName + " 바텀이요.");
        }
    }

    class Nuker implements Champion {
        public void ChampName(String champName) {
            System.out.println(champName + " 미드 선");
        }
    }

    class Jungle implements Champion {
        public void ChampName(String champName) {
            System.out.println(champName + " 정글감");
        }
    }

    class App {
        public static void main(String[] args) {
            Champion champion = LaneCheck.getLane("Bottom");
            champion.ChampName("미스포츈");
            champion = LaneCheck.getLane("Mid");
            champion.ChampName("르블랑");
            champion = LaneCheck.getLane("Anywhere");
            champion.ChampName("마스터 이")
        }
    }


위 예제 중에서 메서드의 리턴타입으로 인터페이스의 타입을 지정한 부분이 있다.

    class LaneCheck {
        public static Champion getLane(String lane) {
                ...
                Champion c = new Jungle();
                return c;
                ...
        }
    }

리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

인터페이스 상속

   인터페이스는 인터페이스로부터만 상속을 받을 수 있으며, 다중상속이 가능하다. 클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.

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

    interface Attackable {
        void attack(Unit u);
    }

    interface Fightable extends Movable, Attackable { }

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

인터페이스의 기본 메서드 (Default Method), 자바 8

   인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 한다.

   인터페이스가 변경되지 않으면 제일 좋지만, 언젠가는 변경이 발생하기 마련이다. 이를 해결하기 위해서 디폴트 메서드(default method)라는 것이 고안되었다. 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새롭게 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

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

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

추상 메서드를 추가하는 대신 디폴트 메서드를 추가하면 조상 클래스에 새로운 메서드를 추가한 것과 동일한 효과를 얻는다.

디폴트 메서드 충돌 규칙

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

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

단순하게 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 해서 해결하는 방법도 있다.

인터페이스의 static 메서드, 자바 8

   Java 8부터 인터페이스에 static 메서드 추가가 가능해졌다. 클래스에서 작성하는 방법과 동일하게 작성할 수 있고, 접근 제어자는 항상 public이며 역시 생략이 가능하다.

static 메서드는 오버라이딩이 불가능하다.

인터페이스의 private 메서드, 자바 9

   Java 9부터 사용할 수 있게된 private 메서드는 다음과 같은 특성을 가지고 있다.

  • 메서드의 몸통 { }이 있고 abstract이 아니다.
  • 구현체에서 구현할 수 없고 자식 인터페이스에서 상속이 불가능하다.
  • static 메서드도 private이 가능하다.

private 메서드는 private, abstract, default 또는 static 메서드를 호출할 수 있다. private static은 static 및 static private 메서드만 호출 할 수 있다.


Constant Interface

Constant Interface는 사용을 추천하지 않는 Anti패턴이다.

   Constant Interface는 오직 상수만 정의한 인터페이스이다. 인터페이스에서 변수를 등록할 때 자동으로 public static final이 붙어서 상수처럼 어디에서나 접근할 수 있다. 또한 하나의 클래스에 여러 개의 인터페이스를 implement 할 수 있는데, Constant Interface를 implement 할 경우, 인터페이스의 클래스명을 네임스페이스로 붙이지 않고 바로 사용할 수 있다.

왜 사용을 추천하지 않는가?

   위키와 Effective Java에 따르면 다음과 같은 이유로 Anti 패턴으로 간주한다.

  1. 사용하지 않을 수도 있는 상수를 포함하여 모두 가져오기 때문에 계속 가지고 있어야한다.
  2. 컴파일할 때 사용되겠지만, 런타임에는 사용할 용도가 없다.
  3. Binary Code Compatibility (이진 호환성)을 필요로 하는 프로그램의 경우, 새로운 라이브러리를 연결하더라도 상수 인터페이스는 프로그램이 종료되기 전까지 이진 호환성을 보장하기 위해 계속 유지되어야 한다.
  4. IDE가 없으면, 상수 인터페이스를 Implement한 클래스에서는 상수를 사용할 때 네임스페이스를 사용하지 않으므로, 해당 상수의 출처를 쉽게 알 수 없다. 또한 상수 인터페이스를 구현한 클래스의 하위 클래스들의 네임스페이스도 인터페이스의 상수들로 오염된다.
  5. 인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할 수 있는지 클라이언트에게 알리는 행위이다. 상수 인터페이스를 구현한다는 사실은 클라이언트에게는 중요한 정보가 아니다. 단지 클라이언트들을 혼란에 빠트릴 뿐이다.
  6. 상수 인터페이스를 Implement한 클래스에 같은 상수를 가질 경우, 클래스에 정의한 상수가 사용되므로 사용자가 의도한 흐름으로 프로그램이 돌아가지 않을 수 있다.

그러면 어떻게 해야하는가?

자바문서에서는 Constant Interface를 Anti 패턴으로 명시하였고 이에 대한 방안으로 import static 구문의 사용을 권장한다.


Reference