[윤성우의 열혈 Java 프로그래밍] Chapter 17 - 인터페이스의 기본과 그 의미

Update:     Updated:

카테고리:

태그:

17-1. 인터페이스의 기본과 그 의미

- 추상 메소드만 담고 있는 인터페이스

interface Printable {
  public void print(String doc);  // 추상 메소드
}

기본 골격은 클래스와 동일하다.
interface라는 선언이 붙고, 추상 메소드(메소드의 몸체가 비어 있는 메소드)로 이루어져있다.
인스턴스 생성이 불가능하고, 다른 클래스에 의해서 구현(implements)된다.

클래스의 인터페이스 구현에는 다음과 같은 특징이 있다.

  1. 구현할 인터페이스를 명시할 때 키워드 implements를 사용한다.
  2. 다중 구현이 가능하다.
  3. 상속과 구현은 동시에 가능하다.

image

또, 인터페이스 관련하여 다음 두 가지 특징도 있다.

  1. 인터페이스의 형을 대상으로 참조변수의 선언이 가능하다.
  2. 인터페이스의 추상 메소드와 이를 구현하는 메소드 사이에 오버라이딩 관계가 성립한다.

- 인터페이스의 본질적 의미

해당 class의 사용방법을 명시한 내용

다음의 예시를 보자.

interface Printable {   // MS가 정의하고 제공한 인터페이스
    void print(String doc);
}

class SPrinterDriver implements Printable {
    @Override
    public void print(String doc) {
        System.out.println("From Samsung printer");
        System.out.println(doc);
    }
}

class LPrinterDriver implements Printable {
    @Override
    public void print(String doc) {
        System.out.println("From LG printer");
        System.out.println(doc);
    }
}

public class PrinterDriver {
    public static void main(String[] args) {
        String myDoc = "This is a report about ...";
        Printable printer;

        // 삼성 프린터로 출력
        printer = new SPrinterDriver();
        printer.print(myDoc);

        System.out.println();

        // LG 프린터로 출력
        printer = new LPrinterDriver();
        printer.print(myDoc);
    }
}

MS사는 인터페이스 Printable을 하나 만들어서 프린터 업체에게 제공하고 있다.
MS사는 자신의 인터페이스를 구현할 클래스의 이름만 알면 될 뿐 내부적으로 구현이 어떻게 이뤄지는지는 알 필요가 없다.
이렇게 인터페이스를 둠으로써 MS사의 윈도우는 삼성과 LG의 프린터를 대상으로 출력을 진행할 수 있게 된다.

17-2. 인터페이스의 문법 구성

인터페이스는 많은 특성이 클래스와 유사하다.

  1. 인터페이스에 존재할 수 있는 메소드: 추상 메소드, 디폴트 메소드, static 메소드
  2. 인터페이스 간 상속도 가능함
  3. 인터페이스의 형(Type) 이름을 대상으로 instanceof 연산도 가능

- 인터페이스에 선언되는 메소드와 변수

인터페이스에 선언되는 메소드와 변수에는 다음과 같은 특징이 있다.

interface Printable {
  int PAPER_WIDTH = 70;
  int PAPER_HEIGHT = 120;
  void print(String doc);
}
  • 메소드
    • 인터페이스의 모든 메소드는 public이 선언된 것으로 간주한다.
    • 인터페이스를 구현하는 클래스는 인터페이스에 존재하는 모든 추상 메소드를 구현해야 한다.
  • 변수
    • 반드시 선언과 동시에 값으로 초기화를 해야 한다.
    • 모든 변수는 public, static, final이 선언된 것으로 간주한다.

- 인터페이스 간 상속

위의 예시 상황에서 컬러 프린터가 추가되었다고 가정해보자!

interface Printable {
    void print(String doc); // 흑백 출력
    void printCMYK(String doc); // 컬러 출력
}

컬러 프린터의 추가 때문에 기존의 Printable 인터페이스에 printCMYK 메소드를 추가하면
-> 이 인터페이스를 기반으로 개발된 드라이버(클래스)를 모두 수정해야 한다!!


따라서 이러한 문제를 해결하기 위해 인터페이스 간의 상속을 지원한다.

interface Printable {
    void print(String doc); // public은 생략되어있으니까 주의하자!
}

interface ColorPrintable extends Printable {    // Printable을 상속하는 인터페이스
    void printCMYK(String doc); // public은 생략되어있으니까 주의하자!
}

class Prn909Drv implements ColorPrintable { // ColorPrintable 뿐만 아니라 Printable에 있는 메소드도 구현해야 함
    @Override
    public void print(String doc) {    // Printable에 있는 메소드
        System.out.println("From MD-909 black & white ver");
        System.out.println(doc);
        System.out.println();
    }

    @Override
    public void printCMYK(String doc) {    // ColorPrintable에 있는 메소드
        System.out.println("From MD-909 CMYK ver");
        System.out.println(doc);
        System.out.println();
    }
}

public class PrintDriver {
    public static void main(String[] args) {
        String mydoc = "This is a report about ...";
        ColorPrintable printer = new Prn909Drv();

        printer.print(mydoc); // 흑백 출력
        printer.printCMYK(mydoc); // 컬러 출력
    }
}

이렇게 인터페이스 간 상속을 통해 기존의 사용 중인 드라이버(클래스)를 수정할 필요가 없게 되었다!

- 인터페이스의 디폴트 메소드

앞선 상황에서는 ‘인터페이스 간의 상속’을 통해 추가할 메소드가 있을 때 이를 구현하고 있는 클래스들에 대한 문제를 해결했다.
하지만, 만약 이미 수십 개의 인터페이스가 있는 상황이라면 어떻게 해야할까?
상속을 통해 문제를 해결하려면 필연적으로 인터페이스의 수가 늘어난다.
그리고 인터페이스의 수가 늘어나는 것은 그 자체로 프로그램 개발에 불편을 초래하는 일이다.

이런 상황을 해결하기 위해 인터페이스의 ‘디폴트 메소드(Default Method)’가 등장했다.

interface Printable {
    void print(String doc);
    default void printCMYK(String doc) {}   // default 메소드 추가. 접근수준지정자 아님!
}

class Prn731Drv implements Printable {  // 최초 출시한 프린터
    @Override
    public void print(String doc) {
        System.out.println("From MD-731 printer");
        System.out.println(doc);
        System.out.println();
    }
}

class Prn909Drv implements Printable {  // 그 이후 출시한 컬러프린터
    @Override
    public void print(String doc) {
        System.out.println("From MD-909 black & white ver");
        System.out.println(doc);
        System.out.println();
    }

    @Override
    public void printCMYK(String doc) { // default method인 printCMYK를 overriding중
        System.out.println("From MD-909 CMYK ver");
        System.out.println(doc);
        System.out.println();
    }
}

public class PrintDriverDefault {
    public static void main(String[] args) {
        String myDoc = "This is a report about ...";

        Printable printer731 = new Prn731Drv();
        Printable printer909 = new Prn909Drv();

        
        printer731.print(myDoc);

        printer909.print(myDoc);
        printer909.printCMYK(myDoc);
    }
}

이러한 default 메소드의 특징은 다음과 같다.

  1. 자체로 완전한 메소드이다.
  2. 따라서 이를 구현하는 클래스가 필수적으로 오버라이딩 하지 않아도 된다.

디폴트 메소드의 존재 의미는 다음과 같다.

인터페이스에 추상 메소드를 추가해야 하는 상황에서
이전에 개발해놓은 코드에 영향을 미치지 않기 위해 등장한 문법이다.

따라서, 처음 인터페이스를 설계하는 과정에서 디폴트 메소드를 정의해 넣는다는 것은
완전히 잘못 이해하고 잘못 사용하고 있는 것이라 할 수 있다.

- 인터페이스의 static 메소드(클래스 메소드)

인터페이스의 static 메소드 또한 클래스의 그것과 유사하다.

interface Printable {
  static void printLine(String str) {
    System.out.println(str);
  }
}

class SimplePrinter {
  public static void main(String[] args) {
    Printable.printLine("end of line"); // 인터페이스의 static 메소드 직접 호출
  }
}

다음, 두 가지 정도만 기억해두자.

  1. 인터페이스에도 static 메소드를 정의할 수 있다.
  2. 인터페이스의 static 메소드 호출 방법은 클래스의 static 메소드 호출 방법과 같다.

- 인터페이스 대상의 instanceof 연산

if (ca instanceof Cake) {...}

클래스를 대상으로 하는 instanceof 연산자는 ca가 참조하는 인스턴스가
‘Cake의 인스턴스’ 이거나 ‘Cake를 상속하는 클래스의 인스턴스’인 경우 true가 반환된다.

이와 유사하게 인터페이스의 경우,
‘Cake를 직접 혹은 간접적으로 구현한 클래스의 인스턴스인 경우’ true를 반환한다.

interface Printable {
  void printLine(String str);
}

class SimplePrinter implements Printable {
  public void printLine(String str) {...}
}

class MultiPrinter extends SimplePrinter {  // Printable을 간접 구현함
  public void printLine(String str) {...}
}

class InstanceofInterface {
  public static void main(String[] args) {
    Printable prn1 = new SimplePrinter();
    Printable prn2 = new MultiPrinter();

    if (prn1 instanceof Printable) {...}  // true
    
    if (prn2 instanceof Printable) {...} // true
  }
}

- 인터페이스의 또 다른 사용 용도: Marker Interface

마커 인터페이스(Marker Interface): 클래스에 특별한 표식을 다는 용도

interface Upper { } // 마커 인터페이스
interface Lower { } // 마커 인터페이스

17-3. 추상 클래스: Abstract Class

상위 클래스로 Design한 class

이 class를 상속하는 하위 class가 추상메소드를 자기가 필요한대로 채워서 썼으면 좋겠어..

public abstract class House { // 추상 클래스
  public void methodOne() {
    System.out.println("method one");
  }

  public abstract void methodTwo(); // 추상 메소드
}

public class MyHouse extends House {
  @Override
  public void methodTwo() {
    System.out.println("method two");
  }
}

추상 클래스의 성격(인터페이스와 유사)

  • 추상 클래스를 대상으로 인스턴스 생성이 불가능하다.
  • 다른 클래스에 의해서 추상 메소드가 구현되어야 한다.

Java lang 카테고리 내 다른 글 보러가기

댓글 남기기