본문 바로가기

JAVA 스터디

[JAVA] 인터페이스(Interface)

다중 상속(multiple inheritance)

자식 클래스가 다양한 부모로부터 클래스를 상속받는 것을 다중 상속이라고 합니다.

그러나 다중 상속을 할 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있습니다.

 

다음은 다중 상속의 문제점에 대한 설명입니다.

다중 상속의 문제

화살표가 상속 관계를 나타낼 때, 'ComboDrive'는 'CDBurner'와 'DVDBurner'를 상속하고 있습니다.

상속에 의해서 ComboDrive는 burn() 메소드를 호출할 수 있습니다.

그러나 어떤 부모 클래스의 burn() 메소드를 호출해야 되는지 명확하지 않은 상황입니다. 

 

따라서 자바는 다중 상속을 지원하지 않습니다.

 

그럼에도 불구하고, 다중 상속은 다양한 동작을 수행할 수 있다는 장점이 있기 때문에 매우 효율적입니다.

자바에서 다중 상속을 지원하는 방식이 바로 인터페이스입니다.

인터페이스(interface)

인터페이스란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는

일종의 추상 클래스를 의미합니다.

 

동일한 목적을 위해 동일한 기능을 수행하게끔 강제하는 것입니다.

인터페이스를 통해서 자바는 다형성을 극대화하고, 개발코드 수정을 줄이며, 프로그램 유지보수성을 높일 수 있습니다.

 

클래스와 유사한 형태를 띄는 참조타입이고, 가질 수 있는 멤버는 상수, 메소드 시그니처, default 메소드, static 메소드

중첩 타입 등으로 제한됩니다.

인터페이스는 인스턴스화 할 수 없으며, 클래스에서 구현하거나 다른 인터페이스에서 인터페이스를 상속할 수 있습니다.

인터페이스 정의하는 방법

클래스 정의와 유사한 방식으로 인터페이스를 정의합니다.

 

접근 제어자, interface 키워드, 인터페이스 이름,

쉼표로 구분된 상위 인터페이스의 목록(Optional), 인터페이스 본문으로 구성됩니다.

 

접근제어자 interface 인터페이스이름 {
    public static final 타입 상수이름 = 값;
    ...
    public abstract 메소드이름(매개변수목록);
    ...
 }

 

인터페이스는 public으로 지정해야 합니다.

그렇지 않으면 동일한 패키지에 정의된 클래스에서만 인터페이스에 접근할 수 있기 때문입니다.

클래스와는 달리 인터페이스의 모든 필드는 public static final이어야 하며, 모든 메소드는 public abstract여야 합니다.

이 부분은 모든 인터페이스에 공통으로 적용되는 부분이므로 이 제어자는 생략할 수 있습니다.

만약 제어자가 생략되면, 컴파일러가 자동으로 추가해줍니다.

 

메소드 시그니처는 중괄호를 입력하지 않고 세미콜론으로 끝냅니다.

 

인터페이스는 여러 인터페이스를 상속할 수 있습니다.

그러므로 선언할 때, extends 키워드 뒤에 여러 개의 상위 인터페이스를 둘 수 있습니다.

인터페이스 구현하는 방법

인터페이스는 추상 클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수 없습니다.

따라서 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야 합니다.

 

인터페이스를 사용할 때는 인터페이스를 implements하는 클래스를 작성해야 합니다.

 

interface Animal {
    void cry();        // 컴파일러가 public abstract 자동 추가
}

class Cat implements Animal {
    public void cry() {
        System.out.println("냐옹냐옹");
    }
}

class Dog implements Animal {
    public void cry() {
        System.out.println("멍멍");
    }
}

 

위와 같이 만든 인터페이스는 API로도 사용할 수 있습니다.

일반적으로 우리 내부의 인터페이스를 제외한 외부에서 제공된 인터페이스를 API라고 생각하면 됩니다.

우리가 API들을 사용할 때, 내부 동작에는 신경쓰지 않고

어떻게 동작할지에 대한 약속만 신뢰하는 것과 동일하다 보면 됩니다.

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

인터페이스를 구현한 클래스를 통해서 인터페이스의 인스턴스화를 진행할 수 있습니다.

interface Animal {
    void cry();        // 컴파일러가 public abstract 자동 추가
}

class Cat implements Animal {            // Animal 구현체 Cat
    public void cry() {
        System.out.println("냐옹냐옹");
    }
}

class Dog implements Animal {            // Animal 구현체 Dog
    public void cry() {
        System.out.println("멍멍");
    }
}

public class Example{
    public static void main(String[] args) {
        Cat c = new Cat();
        Dog d = new Dog();
        
        c.cry();
        d.cry();
    }
}

// 실행결과
냐옹냐옹
멍멍

 

다음과 같이 상속과 구현을 동시에 할 수도 있습니다.

 

class 클래스 이름 extend 상위클래스이름 implements 인터페티스이름 {...}

인터페이스 상속

인터페이스는 인터페이스로부터만 상속받을 수 있습니다.

인터페이스 간의 상속은 클래스 간의 상속과는 달리 다중상속이 가능합니다.

 

interface InterfaceTest1 {
    public final int a = 0;
    public void method1 ();
}

public interface InterfaceTest2 {
    public void method2();
    public void method3();
}

public interface InterfaceTest3 extends InterfaceTest1. InterfaceTest2 {} // 다중상속

 

이렇게 되면 2개의 인터페이스를 상속받은 InterfaceTest3은

InterfaceTest1과 InterfaceTest2의 멤버를 모두 상속받게 됩니다.

 

InterfaceTest3를 클래스에 상속시킨다면 아래와 같이 정의해줘야 합니다.

 

pulic class InterfaceClass implements InterfaceTest3 {
    @Override
    public void method1() {
        // 구현
    }
    @Override
    public void method2() {
        // 구현
    }
    @Override
    public void method3() {
        // 구현
    }
}

 

InterfaceTest1과 InterfaceTest2에 있는 모든 멤버가 InterfaceTest3으로 상속되었기 때문에

InterfaceTest1과 2에 있는 모든 메소드를 구현해야 합니다.

 

아래에 배울 내용은 JAVA 8이 등장하면서 interface에 대한 정의가 몇 가지 변경된 사항들입니다.

인터페이스의 기본 메소드(default method), 자바 8

구현 코드가 없는 인터페이스에서 공통적으로 구현되어야 하는 메소드가 있는 경우

추상 클래스의 구현 메소드처럼 기본적인 구현을 가지는 메소드입니다.

 

예를 들어 계산기의 내용을 담고 있는 인터페이스가 아래와 같이 정의되어 있습니다.

 

public interface Calculator {
    public int plus(int i, int j);
    public int multiple(int i, int j);
}

 

많은 클래스들이 사용하는 상황에서 Calculator 인터페이스에 꼭 필요한 기능이 추가되어야 합니다.

인터페이스가 변경되면, 인터페이스를 구현하는 모든 클래스들이 해당 메소드를 구현해야 하는 문제가 있습니다.

만약 변경 사항을 구현하지 않는다면 컴파일 에러가 발생해서 많은 문제가 발생합니다.

 

이런 문제를 해결하기 위하여 인터페이스에 메소드를 구현해 놓을 수 있도록 변경되었습니다.

 

인터페이스의 메소드 멤버가 default로 선언되면 구현될 수 있습니다.

또한 이를 구현하는 클래스는 default 메소드를 오버라이딩 할 수 있습니다.

 

public interface Calculator {
    public int plus(int i, int j);
    public int multiple(int i, int j);
    default int exec(int i, int j) {            // default로 선언함으로 메소드를 구현
        return i + j;
    }
}

// Calculator 인터페이스를 구현한 MyCalculator 클래스
public class Mycalculator implements Calculator {
    @Override
    public int plus(int i, int j) {
        return i + j;
    }
    
    @Override
    public int multiple(int i, int j) {
        return i * j;
    }
}

public class Example {
    public static void main(String[] args) {
        Calculator cal = new MyCalculator();
        int value = cal.exec(5, 10);
        System.out.println(value);
    }
}

// 실행결과
15

 

 

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

인터페이스의 default 메소드와 마찬가지로 static 키워드 선언된 메소드는 구현 가능합니다.

static 메소드를 호출할 때는 반드시, "인터페이스명.static메소드"의 형태로 호출해야 합니다.

 

public interface Calculator {
    public int plus(int i, int j);
    public int multiple(int i, int j);
    default int exec(int i, int j) {            // default로 선언함으로 메소드를 구현
        return i + j;
    }
    public static int exec2(int i, int j) {     // static 메소드
        return i * j;
    }
}

//인터페이스에서 정의한 static메소드는 반드시 인터페이스명.메소드 형식으로 호출

public class Example {
    public static void main(String[] args){
        Calculator cal = new MyCalculator();
        int value = cal.exec(5, 10);
        System.out.println(value);
        
        int value2 = Calculator.exec2(5, 10);  //static메소드 호출 
        System.out.println(value2);
    }
}

// 실행결과
15
50

 

인터페이스에 static 메소드를 선언함으로써, 인터페이스를 이용하여 간단한 기능을 가지는

유틸리티성 인터페이스를 만들 수 있게 되었습니다.

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

인터페이스 내에서만 사용가능한 메소드이고,

default 메소드나 static 메소드에 사용하기 위해 작성되는 메소드입니다.

인터페이스를 구현하는 클래스쪽에서 재정의하거나 사용할 수 없습니다.

 

public interface Calc {
    int add(int i, int j);
    int multiple(int i, int j);
    //디폴트 메서드
    default void description() {
        System.out.println("정수 계산기를 구현합니다.");
        myMethod(); //private 메서드 사용
    }
    //static메서드
    static int total(int[] arr) {
        int total = 0;
        for(int i :arr) {
            total+= i;
        }
        mystaticMethod();//private static 메서드 사용
        return total;
    }
    //private 메서드
    private void myMethod() {
        System.out.println("private method")
    }
    //private static 메서드
    private static void mystaticMethod() {
        System.out.println("private static method")
    }
}

 

 

default 메소드 description()

: 구현문을 가진 메소드 로 implements하는 클래스들에서도 기본적으로 적용이 되는 메소드 입니다.

 

static메소드  total()

: 인스턴스의 생성없이 사용할 수 있는 메소드 로 인터페이스명을 타입으로 하여 실행할 수 있습니다.

 

private 메소드 myMethod()

: 인터페이스 내에서만 사용가능한 메소드 로 description()안에 사용되었습니다.

 

private static 메소드 mystaticMethod()

인터페이스 내에서만 사용가능하고 static 키워드가 있기에 static 메소드 안에서만 사용가능합니다.

따라서 total에 사용되었습니다.

 

public class CalcTest {
    public static void main(String[] args) {
        Calc calc = new CompleteCalc();
        int n1 = 10;
        int n2 = 2;
		
        System.out.println(calc.add(n1, n2));
        System.out.println(calc.multiple(n1, n2));
        //default 메서드사용
        calc.description();
		
        int[] arr = {1,2,3,4,5};
        //static 메서드 사용
        int sum = Calc.total(arr);
        System.out.println(sum);
    }
}
// 실행결과
12
20
정수 계산기를 구현합니다.
private method
private static method
15