관리 메뉴

개발그래머

[자바스터디 8주차] 인터페이스 본문

Java

[자바스터디 8주차] 인터페이스

임요환 2023. 6. 13. 16:22

목표

  • 자바의 인터페이스에 대해 학습하세요

학습할 것(필수)

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

인터페이스

  • 클래스의 계약 또는 청사진으로 작용하는 참조 유형
  • 구현을 갖지 않는 추상 메소드의 집합을 정의하며 해당 인터페이스를 구현하는 클래스가 제공해야 하는 메소드를 정의함
  • 다른 클래스를 작성할 때 기본이 되는 틀을 제공하며 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스
  • 다중상속
    • 자식 클래스가 여러 부모 클래스를 상속받으면 다양한 동작을 수행할 수 있지만 클래스를 이용해 다중 상속을 하는 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 클래스를 통한 다중상속은 자바에서 지원하지 않음
    • 그렇기 때문에 인터페이스를 통해 다중상속을 지원함
  • 인터페이스는 추상 메소드와 상수만을 포함할 수 있음
    • 자바 버전이 올라가며 다양한 접근 제어자 메소드들도 포함할 수 있게 변경됨
    • 추상클래스는 추상 메소드뿐만 아니라 생서자, 필드, 일반 메소드도 포함할 수 있음

인터페이스의 장점

  • 추상화와 계약 선언 = 클래스가 구현해야 하는 메소드를 정의하여 일관성과 유지보수성 향상
  • 다중 상속과 유연성 = 다중 상속의 효과를 얻어 여러 클래스의 기능을 조합하여 유연하고 재사용 가능한 코드 작성
  • 다형성 구현 = 다양한 구현체를 동일한 인터페이스로 다루어 코드의 유연성과 확장성 증가
  • 코드의 모듈화와 재사용성 = 코드 모듈화 및 구현 세부 정보 감추기로 코드 재사용성 및 유지보수 비용 감소
  • 표준화와 협업 = 표준화된 계약 제공으로 협업과 의사소통 간소화, 모듈 간 상호 작용 정의 및 개발자 역할 분리 가능

인터페이스 정의하는 방법

// 접근제어자 interface 인터페이스 이름
public interface MyInterface {
    // 상수 필드
    int MAX_VALUE = 10;
    // public static final int MAX_VALUE = 10;

    // 추상 메소드 선언
    void method1();
    int method2(String str);
    void method3(int num);
    // public abstract void method3(int num);
}
  • 기능에 대한 구현보다 선언에 초점을 맞춰 사용
  • 인터페이스 내에 존재하는 메소드는 public abstract로 선언되며 생략가능
  • 인터페이스 내에 존재하는 필드는 public static final로 선언되며 생략가능
  • 컴파일 시 자바 컴파일러가 자동으로 생략된 제어자를 추가해 줌

인터페이스 구현하는 방법

// 접근제어자 class 클래스이름 implements 인터페이스이름
public class MyClass implements MyInterface {
    // 인터페이스 메소드 구현
    public void method1() {
        // 메소드 구현
        System.out.println("method1");
    }

    public int method2(String str) {
        // 메소드 구현
        System.out.println("method2 : " + str);
        return 0;
    }

    public void method3(int num) {
        // 메소드 구현
        System.out.println("method3");
    }
}
  • implements를 사용하여 새로운 클래스를 작성해 구현해야 함
  • 인터페이스는 추상클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수 없음
  • 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해야만 함

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

public class MyClass implements MyInterface {
    //생략
    ...

    public static void main(String[] args) {
        MyInterface myInterface = new MyClass();
        myInterface.method1();
    }
}
  1. 인터페이스를 선언하고 필요한 메소드를 정의
  2. 인터페이스를 구현하는 클래스 작성
  3. MyInterface myInterface = new MyClass(); -> 인터페이스의 레퍼런스를 선언하고 구현체의 객체를 할당
  4. myinterface.method1() -> myinterface를 사용하여 구현체의 메소드를 호출

인터페이스 상속

public interface ParentInterface {
    void parentMethod();
}

public interface ChildInterface extends ParentInterface {
    void childMethod();
}
  • extends 키워드를 사용하여 인터페이스에서 인터페이스를 상속할 수 있음
  • 인터페이스의 상속은 자바에서 인터페이스 간의 관계를 나타내는 개념임
  • 클래스와 다르게 다중상속을 지원하며 인터페이스가 인터페이스를 상속할 수도 있음
  • 인터페이스 간의 계층 구조를 형성하고 코드의 재사용성을 높일 수 있음
  • ChildInterface를 구현하는 구현체는 parentMethod와 childMethod를 모두 구현해야 함
public interface InterfaceA {
    void methodA();
}

public interface InterfaceB {
    void methodB();
}

public interface InterfaceC extends InterfaceA, InterfaceB {
    void methodC();
}
  • 인터페이스는 다중 상속을 지원함
  • InterfaceC를 구현하는 구현체는 methodA, methodB, methodC를 모두 구현해야 함
  • 인터페이스의 상속을 통해 계층적인 구조를 형성할 수 있으며 코드의 구조화와 재사용성을 증가시킬 수 있음
  • 인터페이스의 상속은 다형성을 강화하고 유연성을 제공하여 다양한 인터페이스 타입으로 객체를 다룰 수 있음

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

public interface JoinMember {
    void preJoin();
    void afterJoin();
}
  • Java8 이전에는 인터페이스에서는 나는 preJoin 만 구현하고 싶었는데 DefaultJoinMember 자체를 implements 하게 되면 preJoin, afterJoin 둘 다 구현해야 했음
  • 그래서 interface 자체를 implements 하는 게 아니라 interface를 implements 한 adapter 클래스를 extends 하여 사용하게 됨
public class JoinMemberAdapter implements JoinMember {
    // 원하는 메소드만 사용하기 위한 adapter 클래스
    // 이게 adapter 패턴은 아니고 편의성으로 사용하는 거임
    // HandlerInterceptor 클래스, XXXConfigurer, XXXConfiguration, XXXConfigurerAdapter
    @Override
    public void preJoin() {
        System.out.println("반갑습니다");
    }

    @Override
    public void afterJoin() {
        System.out.println("가입감사합니다");
    }
}

public class HelloJoinMember extends JoinMemberAdapter {
    @Override
    public void preJoin() {
        System.out.println("반갑습니다.");
    }
}
  • JoinMemberAdapter를 extends 하여 원하는 메소드만 구현
  • 이 경우 상속은 한 번밖에 되지 않기 때문에 추가 상속을 사용하기가 어려움 -> interface로 바뀌면 상속을 추가로 가능하게 됨
public interface DefaultJoinMember extends StaticJoinMember {
    default void preJoin(){
        System.out.println("멤버 반갑습니다");
    }

    default void afterJoin(){
        System.out.println("멤버 가입감사합니다");
    }
}

public class DefaultHelloJoinMember implements DefaultJoinMember {
    @Override
    public void preJoin() {
        System.out.println("반갑습니다.");
    }
}
  • Java8 이후에는 default를 사용할 수 있게 되어 추가로 adapter class를 두지 않아도 됨
  • 인터페이스에서 기본적인 구현을 제공하는 데 사용되며 default 메소드는 인터페이스에서 메소드를 정의하고 해당 인터페이스를 구현한 클래스에서 선택적으로 오버라이딩할 수 있음

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

public interface StaticJoinMember {
    static void preJoin() {
        System.out.println("static pre join member");
    }

    static void afterJoin() {
        System.out.println("static after join member");
    }
}

public class StaticJoinMemberImpl implements StaticJoinMember {

    public static void main(String[] args) {
        StaticJoinMemberImpl member = new StaticJoinMemberImpl();
//        member.preJoin(); // 불가능
//        StaticJoinMemberImpl.preJoin(); // 불가능
        StaticJoinMember.preJoin(); // 이렇게만 사용 가능
    }
}
  • 이전에는 인터페이스에서는 추상 메소드만 정의할 수 있었지만, static 메소드를 추가함으로써 인터페이스에 유틸리티 메소드나 기본 구현을 제공할 수 있게 됨

default 메소드, static 메소드 백기선님 팁

  • 기본 메소드와 static 메소드는 하위 호환성을 위해 등장함 -> 예를 들어 내가 어떤 오픈소스를 사용하고 있는데 그 오픈소스에서 기능을 추가하기 위해 interface에 메소드를 추가할 시 기존에 사용하던 사용자들 그 메소드를 구현을 하지 않아 갑자기 에러가 발생할 것 -> 이를 방지하기 위해 등장

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

public interface DefaultJoinMember {
    default void preJoin(){
        printMessage("멤버 반갑습니다");
    }

    default void afterJoin(){
        printMessage("멤버 가입감사합니다");
    }

    private void printMessage(String message) {
        System.out.println(message);
    }
}
  • pirvate 메소드는 인터페이스 내부에서만 호출할 수 있음
  • default 메소드와 함께 사용하여 default 메소드의 구현을 지원하고 코드 중복을 방지하며 코드의 가독성을 높이는 데 사용함