언어(Language)/Java

[Java] 자바 인터페이스(Interface) 개념 정리 및 활용

잇트루 2022. 9. 18. 06:00
반응형

인터페이스 (Interface)

프로그래밍에서의 인터페이스는 서로 다른 두 시스템, 장치, 소프트웨어를 서로 이어주는 부분 또는 접속 장치로 정의할 수 있다.

컴퓨터의 내용을 그래픽 형태로 이미지화하여 사용자와 소통할 수 있도록 연결하는 GUI(Graphic User Interface)가 인터페이스의 대표적인 예시이다.

 

자바의 인터페이스 또한 유사한 기능을 가지고 있으며, 추상 클래스와 함께 자바에서 추상화를 구현하는 핵심적인 역할을 담당한다. 추상 클래스에 비해 더 높은 단계의 추상성을 가지는 것이 바로 인터페이스다.

 

추상 클래스는 메서드 바디가 없는 추상 메서드를 하나 이상 포함하는 점 외에는 일반 클래스와 동일하다. 하지만, 인터페이스는 기본적으로 추상 메서드와 상수(static 변수)만을 멤버로 가질 수 있다는 점에서 추상 클래스에 비해 추상화 단계가 더 높다고 할 수 있다.

  • 자바 8 이후 버전은 default/static 메서드를 인터페이스에 포함시킬 수 있다.

 

즉, 인터페이스는 추상 클래스와는 다르게 기본적으로 추상 메서드들의 집합으로 이루어져 있다.

 

인터페이스의 기본 구조

인터페이스는 기본적으로 클래스를 작성하는 것과 유사하지만, class 키워드 대신 interface 키워드를 사용한다.

또한, 일반 클래스와는 다르게 인터페이스 내부의 모든 필드가 public static final로 정의되고, static과 default 메서드를 제외한 모든 메서드가 public abstract로 정의된다.

 

따라서 필드와 메서드를 정의할 때 따로 위 내용을 명시하지 않아도 된다.

public interface InterfaceEx {
    // 인터페이스의 인스턴스 변수 정의
    // 아래 모두 public static final로 정의됨.
    public static final int a = 1;
    final int b = 2;    // public static 생략한 경우
    static int c = 3;   // public과 final을 생략한 경우
    int d = 4;          // 모두 생략한 경우

    // 인터페이스의 메서드 정의
    public abstract int geInt();
    void general(); // public abstract 생략 가능
}

생략된 부분은 자바 컴파일러가 자동으로 추가해 주기 때문에 생략 가능하다.

 

인터페이스 구현

인터페이스도 추상 클래스와 같이 자체로 인스턴스를 생성할 수 없고, 메서드 바디를 정의하는 클래스를 따로 작성해야 한다.

추상 클래스의 extends 키워드를 통해 클래스의 상속을 받는 것과 동일하지만, 인터페이스는 ‘구현하다’라는 의미를 가진 implements 키워드를 사용한다.

 

특정 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 추상 메서드를 구현해야 한다.

즉, 인터페이스를 구현한다는 것은 클래스에게 인터페이스의 추상 메서드를 구현하도록 강제하는 것을 의미한다. 인터페이스가 가진 모든 추상 메서드는 해당 클래스 내부에서 오버라이딩을 하여 완성하게 된다.

class ClassEx implements InterfaceEx {
    // 인터페이스에 정의된 추상 메서드 구현
}

 

인터페이스 다중 구현

먼저, 하위 클래스는 다중으로 상위 클래스에게 상속받을 수 없다. 즉, 하위 클래스는 단 하나의 상위 클래스만을 상속받을 수 있다.

하지만, 인터페이스는 다중적 구현이 가능하다. 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있다.

class ClassEx implements InterfaceEx1, InterfaceEx2, InterfaceEx3, ... {

}

다음과 같이 다중 인터페이스 구현을 활용할 수 있다.

 

인터페이스 다중적 구현이 가능한 이유 클래스의 다중 상속이 안 되는 이유와 연관이 깊다.

클래스는 부모 클래스의 동일한 이름의 필드나 메서드가 존재하는 경우 충돌이 발생하기 때문이다.

하지만, 인터페이스는 애초에 미완성된 멤버를 가지고 있기 때문에 충돌이 발생할 여지가 없어 안전하게 다중 구현이 가능한 것이다.

public interface Country {
    void country();
}

interface Greetings {
    void greetings();
}

class Korean implements Country, Greetings {
    public void country() {
        System.out.println("대한민국");
    }

    public void greetings() {
        System.out.println("안녕하세요");
    }
}

class American implements Country, Greetings {
    public void country() {
        System.out.println("미국");
    }

    public void greetings() {
        System.out.println("Hello");
    }
}
class GreetingsEx {
    public static void main(String[] args) {
        Korean korean = new Korean();
        American american = new American();

        korean.country();
        korean.greetings();

        american.country();
        american.greetings();
    }
}

 

뿐만 아니라, 특정 클래스는 다른 클래스로부터 상속을 받으면서 동시에 인터페이스를 구현할 수 있다.

abstract class Country {
    public abstract void country();
}

    interface Greetings {
    void greetings();
}

class Korean extends Country implements Greetings {
    public void country() {
        System.out.println("대한민국");
    }

    public void greetings() {
        System.out.println("안녕하세요");
    }
}

class American extends Country implements Greetings {
    public void country() {
        System.out.println("미국");
    }

    public void greetings() {
        System.out.println("Hello");
    }
}
class GreetingsEx {
    public static void main(String[] args) {
        Korean korean = new Korean();
        American american = new American();

        korean.country();
        korean.greetings();

        american.country();
        american.greetings();
    }
}

 

인터페이스의 장점

역할과 구현을 분리시켜 사용자 입장에서 복잡한 구현의 내용 또는 변경과 상관없이 해당 기능을 사용할 수 있다.

다음과 같이 인터페이스를 정의한 후, 각 구현체에 implements 키워드를 사용하여 각각의 기능을 구현할 수 있다.

interface Country {
    public abstract void greetings();
}

class User {
    public void callCountry(Country country) {
        country.greetings();
    }
}

class Korean implements Country {
    public void greetings() {
        System.out.println("대한민국");
        System.out.println("안녕하세요");
    }
}

class American implements Country {
    public void greetings() {
        System.out.println("미국");
        System.out.println("Hello");
    }
}

public class InterfaceEx {
    public static void main(String[] args) {
        User user = new User();
        user.callCountry(new Korean());
        user.callCountry(new American());
    }
}

User 클래스에서 매개변수의 다형성을 활용하여, 인터페이스를 매개변수로 받을 수 있다.

그로 인해 Korean 클래스와 American 클래스의 내용이 변경되더라도, User 클래스는 더 이상 코드를 변경하지 않아도 되는 효과를 얻는다.

즉, 코드 변경을 최소화하고 해당 기능을 사용할 수 있도록 하는 것이다.

이러한 방법은 개발기간을 단축할 수 있고, 독립적인 프로그래밍을 통해 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화할 수 있다.

반응형