8회차. 인터페이스

KIMA·2023년 1월 30일
0
post-thumbnail

목표

자바의 인터페이스에 대해 학습하기

학습할 것

인터페이스 정의

인터페이스란? (java 8 버전 이전)

  • 인터페이스는 일종의 추상클래스이다.
  • 인터페이스도 추상클래스처럼 추상메서드를 갖지만 추상화 정도가 높아서 구현부를 갖춘 일반 메서드 또는 멤버변수를 가질 수 없다.
    • 추상클래스 : 부분적으로만 완성된 미완성 설계도
    • 인터페이스 : 완성된 것은 아무 것도 없고, 밑그림만 그려져 있는 기본 설계도
  • 오직 추상메서드와 상수만을 멤버로 가진다.
    • 상수 : 인터페이스에서 값을 정하므로, 구현체는 해당 값을 변경하지않고 그대로 사용
      • 절대적
    • 추상 메소드 : 인터페이스는 가이드만 주고, 구현체는 해당 메소드를 오버라이딩하여 사용
      • 강제적
  • 장점
    • 첫째, 클래스를 개발하기 전에 클래스들이 공통적으로 가져야할 기능에 대해 설계도(인터페이스)를 미리 만들어놓음으로써, 서로 다른 개발자들이 각각의 클래스들을 완성할 때까지 기다리지 않고 미리 제작해놓은 설계도만 보고 클래스를 각자 완성하여 개발자간의 의존성을 낮출 수 있다.
      • 따라서, 개발자들이 동시에 개발할 수 있으므로 개발 기간을 단축시킬 수 있다.
    • 둘째, 클래스의 기본 설계도를 제공하여 개발자들에게 정형화된 개발을 강요할 수 있다.
    • 셋째, 서로 관계없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.
    • 넷째, 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에, 실제 구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 다른 클래스에 영향을 미치지 않으므로 클래스 간의 결합도를 낮출수 있다.
  • 사용 예시 - DB를 추후 변경해야할 때
    • 특정 DB를 사용하는데 필요한 클래스를 사용해서 프로그램을 작성했다고하자.
    • DB를 변경해야한다면, 전체 프로그램 중에서 DB 관련된 부분은 모두 변경해야한다.
    • 하지만, DB 관련 인터페이스를 정의하고 이를 이용한 클래스를 사용해서 프로그램을 작성했다고 하자.
    • DB를 변경해야한다면, 구현 클래스만 변경시켜줄 뿐 인터페이스는 그대로이므로 프로그램을 변경하지 않아도 된다.

java 8 버전 이후의 인터페이스 변화

  • 자바 8 버전 이후, 인터페이스에서 메소드에 대한 가이드 뿐만 아니라 구현까지할 수 있는 디폴트 메소드와 정적 메소드가 추가되었다.
  • 디폴트 메소드 : 인터페이스에서 기본적으로 제공해주지만, 구현체가 구현 내용을 오버라이딩할 수도 있다.
    • 선택적
  • 정적 메소드 : 인터페이스에서 제공해준 메소드로, 구현체는 해당 메소드를 오버라이딩할 수 없다.
    • 절대적
  • 장점
    • 두 가지 메소드를 통해 구현 강제성 안에 유연함을 추구할 수 있게 되었다.
    • 인터페이스에 새로운 기능을 추가할 때, 해당 인터페이스를 구현한 클래스들이 새로운 기능 추가로 인한 영향을 받지 않는다.
      • 기존의 추상메소드는 구현 클래스가 반드시 구현부를 작성해야했으므로, 새로운 기능이 추가되면 모든 구현 클래스들이 영향을 받게 된다.
  • 단점 : 인터페이스를 구현한 구현체를 만든 뒤에 추후 디폴트나 정적 메소드를 추가하여 해당 메소드가 기존에 구현한 메소드를 사용할 경우, 구현체는 메소드가 새로 추가된 사실을 모를수도 있으므로 리스크가 존재한다.
    • 사용하게 된다면, 구현체가 잘못 사용하지않도록 반드시 문서화해야한다.

🤔 인터페이스가 메소드에 대한 구현까지할 수 있다고? 그러면 추상클래스랑 똑같은 거 아님?

  • 추상클래스는 is a 개념이다.
    • 예) 추상클래스 - 컴퓨터(Computer), 추상클래스를 상속한 클래스 - 노트북(Notebook)
      • 노트북은 컴퓨터이다.
      • 즉, 노트북은 컴퓨터(데스크탑, 노트북 등) 중 하나이다.
      • 노트북은 컴퓨터 계열이므로 또 다른 추상클래스인 세탁기(Washer)를 상속받을 수 없다.
  • 인터페이스는 has a 개념이다.
    • 예) 인터페이스 - 코딩 가능(Developable), 인터페이스를 구현한 클래스 - 홍길동
      • 홍길동은 코딩할 수 있다.
    • 즉, 홍길동의 능력중 하나가 코딩인 것이다.
      • 인터페이스는 기능을 의미하므로 인터페이스의 이름에는 able로 끝나는 것들이 많다.
    • 홍길동은 또 다른 인터페이스인 요리(Cookable)도 구현하여 코딩뿐만 아니라 요리도 할 수 있는 등의 여러가지 능력을 가질 수 있다.

인터페이스 정의

접근제어자 interface 인터페이스명 {
  (public static final) 타입 상수명 =;
  (public abstract) 리턴타입 메소드명(파라미터...);
  static 리턴타입 메소드명(파라미터...) {...}
  default 리턴타입 메소드명(파라미터...) {...}
}
  • 접근제어자 : public, default
  • 모든 멤버변수는 public static final이어야 하며, 이를 생략시 컴파일러가 자동으로 추가해준다.
    • 대부분 생략하는 편이다. 따라서 구현 클래스는 반드시 메소드의 접근 제어자를 public으로 설정해야 함을 잊지말자.
  • 모든 메서드는 public static이어야 하며, 이를 생략시 컴파일러가 자동으로 추가해준다.
    • 대부분 생략하는 편이다. 따라서 구현 클래스는 반드시 메소드의 접근 제어자를 public으로 설정해야 함을 잊지말자.
    • 단, 디폴트 메소드와 정적 메소드는 예외

인터페이스 상속

: 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중 상속이 가능하다.

  • 인터페이스의 추상 메소드는 구현부가 없어, 여러 인터페이스의 추상 메소드를 상속받아도 구현부가 없는 사실은 같기 때문에 다중 상속이 가능하다.

  • 부모 인터페이스의 멤버를 모두 상속받는다.

  • 예제

    interface Movable { // 이동할 수 있는 기능
      void move(int x, int y); // 지정된 위치(x, y)로 이동하는 메소드
    }
    interface Attackable { // 공격할 수 있는 기능
      void attack(Unit u); // 지정된 유닛을 공격하는 메소드
    }
    
    interface Fightable extends Movable, Attackable {} // 싸울 수 있는 기능 <- 이동 / 공격 가능

🤔 클래스에서 두 인터페이스를 구현하였는데, 두 인터페이스에 메소드 시그니처가 동일한 디폴트 메소드가 존재한다면?
: 구현 클래스에서 중복되는 인터페이스의 디폴트 메소드를 재정의하여 사용한다.

  • 재정의하지 않으면 컴파일 에러가 발생한다.
  • 스태틱 메소드가 아니므로 참조할 수 있는 방법이 아래와 같다.
    • 인터페이스.super.디폴트메서드()
interface JoinMember { 
  default void preJoin() {
    System.out.println("pre member");
  }
}
interface JoinGroup { 
  default void preJoin() {
    System.out.println("pre group");
  }
}
class Member implements JoinMember, JoinGroup {
  @Override
  public void preJoin() {
      JoinMember.super.preJoin();
      JoinGroup.super.preJoin();
  }
}

인터페이스 구현

: 추상 클래스와 마찬가지로 인터페이스 그 자체로 인스턴스를 생성할 수 없으므로 반드시 구현 클래스를 생성한 후 사용한다.

  • 문법

    class 클래스명 implements 인터페이스1, 인터페이스2, ... {
      // 인터페이스의 추상 메소드를 구현한다.
    }
  • 예제
    인터페이스 구현 예제

    class Unit {
      int currentHP; // 현재 체력 
      int x;
      int y;
    }
    
    interface Movable { // 이동할 수 있는 기능
      void move(int x, int y); // 지정된 위치(x, y)로 이동하는 메소드
    }
    interface Attackable { // 공격할 수 있는 기능
      void attack(Unit u); // 지정된 유닛을 공격하는 메소드
    }
    interface Fightable extends Movable, Attackable {} // 싸울 수 있는 기능 <- 이동 / 공격 가능
    
    class Figther extends Unit implements Fightable {
    	@Override
      public void move(int x, int y) { ... }
      
      @Override
      public void attack(Unit u) { ... }
    }

인터페이스의 메소드 중 일부만 구현하고 싶을 때

  • 자바 8버전 이전
    : 인터페이스와 구현 클래스 중간에 추상 클래스를 두어 인터페이스의 추상 메소드를 추상 클래스가 빈 내용으로 구현하고 구현 클래스는 해당 추상 클래스를 상속받아 원하는 메소드만 오버라이딩해 사용한다.

    • 단점 : 추상 클래스를 상속해야하므로 다른 클래스를 상속할 수 없게된다.
    interface JoinMember {
      void preJoin();
      void afterJoin();
    }
    class JoinMemberAdater implements JoinMember {
      @Override
      void preJoin() {}
    
      @Override
      void afterJoin() {}
    }
    class HelloJoinMember implements JoinMemberAdater {
      @Override
      void preJoin() {
        System.out.println("안녕하세요.");
      }
    }
    class NotiJoinMember implements JoinMemberAdater {
      @Override
      void afterJoin() {
        System.out.println("멤버용 새 글이 올라왔어요.");
      }
    }
  • 자바 8버전부터
    : 구현 클래스는 인터페이스의 디폴트 메소드를 재정의하여 사용한다.

    interface JoinMember {
      default void preJoin() {}
      default void afterJoin() {}
    }
    class HelloJoinMember implements JoinMember {
      @Override
      void preJoin() {
        System.out.println("안녕하세요.");
      }
    }
    class NotiJoinMember implements JoinMember {
      @Override
      void afterJoin() {
        System.out.println("멤버용 새 글이 올라왔어요.");
      }
    }

익명 클래스

등장 배경

  • 인터페이스를 구현하는 클래스를 만들어 사용하는 것이 일반적이고, 해당 구현 클래스를 재사용할 수 있다는 장점이 있다.
  • 하지만 구현 클래스를 객체로 생성하는 횟수가 단 한번이라면, 일회성의 구현 객체를 만들기 위해 소스 파일을 만들고 클래스를 선언하는 것은 비효율적이다.
  • 이를 위해 자바는 소스 파일을 따로 만들지 않고도 클래스 선언과 동시에 구현 객체를 만들 수 있는 방법을 제공하며, 이것을 익명 클래스라고 한다.

익명 클래스란?

: 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이며, 따로 소스 파일을 생성하여 클래스를 선언할 필요가 없다.

  • 클래스의 선언과 객체의 생성을 동시에 한다.

  • 이름이 없기 때문에 생성자를 가질 수 없다.

  • 익명 클래스를 컴파일하면 외부 클래스명$숫자.class의 형식으로 클래스파일명이 결정된다.

  • 문법

    인터페이스명 변수명 = new 인터페이스명() { 
      // 인터페이스의 추상 메소드 구현
    }
    조상클래스명 변수명 = new 조상클래스명() {
    
    }
  • 예제

    interface Movable { // 이동할 수 있는 기능
      void move(int x, int y); // 지정된 위치(x, y)로 이동하는 메소드
    }
    interface Attackable { // 공격할 수 있는 기능
      void attack(Unit u); // 지정된 유닛을 공격하는 메소드
    }
    interface Fightable extends Movable, Attackable {} // 싸울 수 있는 기능 <- 이동 / 공격 가능
    Fightable fighter = new Fightable() {
    	@Override
      public void move(int x, int y) { ... }
      
      @Override
      public void attack(Unit u) { ... }
    }

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

: 인터페이스 레퍼런스는 다형성을 통해 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.

  • 해당 인터페이스에 선언된 메서드만 호출할 수 있다.

인터페이스의 private 메소드 in 자바 9 버전

: 오직 인터페이스 내부에서만 사용할 수 있는 구현부를 가진 메소드

  • 등장 배경
    • java8의 디폴트 메소드와 정적 메소드는 단지 인터페이스 내부에서만 사용되는 메소드일 뿐일 때도(중복 코드 extract등을 목적으로하는) public 접근제어자로 인해 인터페이스 구현 클래스가 해당 메소드를 필요로하지않아도 반드시 상속받게 된다.
  • 구현체에서 구현할 수 없고, 자식 인터페이스에서 상속이 불가능하다.
  • private 메소드와 static private 메소드가 존재한다.

Reference

profile
안녕하세요.

0개의 댓글