2022.08.02/자바 정리/LinkedList 구현 및 사용/메서드 사용법을 규격화하여 객체 사용 방법을 일관성 있게 만들기: 인터페이스/인터페이스 사용법

Jimin·2022년 8월 2일
0

비트캠프

목록 보기
14/60
  • board-app 프로젝트 수행
      1. 자료구조를 구현하고 다루는 방법: Linked List 구현 및 사용
      • 021 버전에 LinkedList 적용
      1. 메서드 사용법을 규격화하여 객체 사용 방법을 일관성 있게 만들기: 인터페이스
      1. List 규격에 맞춰 LinkedList 구현하고 적용하기
  • 인터페이스 사용법(eomcs-java-lang: com.eomcs.oop.ex09.*)

공통 코드(필드, 메서드)를 공유하는 방법: 연관(association)

  • BoardDao 클래스와 MemberDao 클래스가 ObjectList 클래스에서 LinkedList 클래스를 위임하는 것으로 교체한다.
    • ObjectList 클래스의 메서드 대신, LinkedList 클래스의 메서드 호출
  • 상속이 아닌 위임으로 XxxList를 가져왔기 때문에, 교체를 해도 XxxHandler 클래스까지 변경하지 않아도 된다.
    ⇒ 위임의 편리성을 확인할 수 있다!

board-app project

자료구조를 구현하고 다루는 방법: LinkedList 구현 및 사용

  • 021 버전을 바탕으로LinkedList 적용한다. (021 버전은 연관, 위임을 사용하여 만든 LinkedList와 연결한 것)
  • 상속이 아닌 연관(위임) 구조 일 때 기능 교체의 이점을 확인한다.
    • 상속과 위임의 영향 범위
  1. 상속의 경우
  • BoardList가 ObjectList의 메서드르르 오버로딩/오버라이딩 한다.
  • 수퍼 클래스를 교체하게 되면, 오버로딩/오버라이딩 했던 메소드들을 모두 다시 변경해주어야 한다.
  • 또한 이 서브 클래스를 사용하는 다른 클래스들(XxxHandler)들도 수정해주어야 한다.
    ⇒ 상속은 강하게 결합되어 있기 때문에, 기능 변경이나 교체가 어렵다.
  • 전체 사용: BoardHandler는 BoardList의 기능만 사용하는 것이 아니라, BoardList가 상속 받은 기능도 사용한다.
    → BoardList에 수퍼 클래스를 변경하면, BoardHandler도 영향을 받는다.
    → 코드 변경을 했을 때 영향을 받는 범위가 넓다. ⇒ 유지보수가 어렵다.
    (노란색 박스만큼 BoardHandler가 사용하고 있어서 노란색 박스 포함 BoardHandler까지 전부 변경해줘야 한다.)
  1. 위임의 경우
  • 교체한 수퍼클래스의 메서드에 맞추어 메서드 호출을 변경한다.
  • 이 서브 클래스를 사용하는 다른 클래스는 변경할 필요가 없다.
    ⇒ 위임은 기능 변경과 교체가 쉽다.
  • 일부 사용: BoardHandler는 BoardDao에 있는 기능만 사용한다, ObjectList에는 접근도 하지 않는다.
    ⇒ ObjectList를 변경하더라도 BoardHandler에는 영향이 없다.
    (BoardHandler가 노란색 박스만 사용하고 있다. BoardDao만 수정해주면 된다.)

1단계 - LinkedList를 구현한다.

  • 020 버전에서 구현한 LinkedList관련 클래스를 가져온다.

2단계 - BoardDao와 MemverDao는 ObjectList 대신 LinkedList를 사용한다.

  • BoardDao 클래스 변경
    • 의존객체를 ObjectList 에서 LinkedList로 교체한다.
  • MemberDao 클래스 변경
    • 의존객체를 ObjectList 에서 LinkedList로 교체한다.
  • BoardDao class
// 게시글 목록을 관리하는 역할
//
public class BoardDao {
  LinkedList list = new LinkedList();

  private int boardNo = 0;

  public void insert(Object e) {

    Board board = (Board) e;
    board.no = nextNo();
    list.append(e);
  }

  public Board findByNo(int boardNo) {

    for (int i = 0; i < list.length(); i++) {
      Board board = (Board) list.retrieve(i);
      if (board.no == boardNo) {
        return board;
      }
    }

    return null;
  }

  public boolean delete(int boardNo) {

    for (int i = 0; i < list.length(); i++) {
      Board board = (Board) list.retrieve(i);
      if (board.no == boardNo) {
        return list.delete(i) != null;
      }
    }

    return false;
  }

  public Board[] findAll() {

    Object[] arr = list.getArray();

    Board[] boards = new Board[arr.length];

    for (int i = 0; i < arr.length; i++) {
      boards[i] = (Board) arr[i];
    }

    return boards;
  }

  private int nextNo() {
    return ++boardNo;
  }
}
  • MemberDao class
public class MemberDao {

  // MemberDao 가 사용할 의존 객체를 선언한다.
  ObjectList list = new ObjectList();

  // ObjectList를 상속 받지 않기 때문에 
  // 목록에 데이터를 추가하고 싶다면 
  // MemberDao 클래스에 해당 메서드를 직접 정의해야 한다.
  // 물론, 실제 작업은 ObjectList 가 할 것이다.
  //
  public void insert(Member member) {
    list.add(member);
  }

  // MemberList 에서 MemberDao 로 바꿔는 것에 맞춰
  // 메서드의 이름도 데이터에 초점을 맞춰 변경한다.
  //
  public Member findByEmail(String email) {
    for (int i = 0; i < list.size(); i++) {
      Member member = (Member) list.get(i);
      if (member.email.equals(email)) {
        return member;
      }
    }
    return null;
  }

  public boolean delete(String email) {
    for (int i = 0; i < list.size(); i++) {
      Member member = (Member) list.get(i);
      if (member.email.equals(email)) {
        return list.remove(i);
      }
    }
    return false;
  }

  public Member[] findAll() {
    //목록에 저장된 회원 데이터를 가져온다.
    Object arr[] = list.toArray();
    //Object배열의 값을 Member배열로 옮긴다.
    Member [] members = new Member[arr.length];
    for(int i=0; i < arr.length; i++) {
      members[i] = (Member)arr[i];
    }
    return members;
  }
}



메서드 사용법을 규격화하기: 인터페이스(Interface)

메서드 형식

  • Method signature
  • Function prototype
    • 메서드명
    • 파라미터 선언
    • 리턴 타입
  1. 기존 방식: 규격화가 안 된 경우
  • ObjectList의 메서드와 LinkedList의 메서드 형식(메서드명, ...)이 다르기 때문에 호출 코드를 변경해야 한다.
  1. 개선 방식: 매서드 호출을 규격화 한 경우

같은 인터페이스를 구현한 클래스끼리는 메서드 규격이 같기 때문에 클래스가 교체되더라도 코드 변경을 최소화 시킬 수 있다.


board-app project

메서드 사용법을 규격화하여 객체의 사용방법을 일관성 있게 만들기: 인터페이스

  • 021 버전의 코드를 가져온다.(ObjectList를 상속이 아닌 위임으로 바꾼 상태)
  • 인터페이스 문법을 이용하여 목록 다루는 객체의 사용법을 규격화한다.
  • ObjectList를 List규격에 맞추어 구현(implements)한다.
  • <<interface>> List: 목록을 다루는 방식을 규격화한다.
    ⇒ 메서드 호출 규칙(규격화)

0단계 - 코드 준비

  • 021 버전의 소스를 가져온다. (ObjectList를 상속이 아닌 위임으로 바꾼 상태)

1단계 - 목록을 다루는 객체가 가져야할 메서드의 형식을 정한다.

  • List 인터페이스 생성
    • 목록을 다루는 메서드를 선언한다.
  • List interface
public interface List {
  void add(Object value);
  Object get(int index); // 목록에서 인덱스에 해당하는 항목을 꺼내는 메서드의 형식.
  Object remove(int index); // 목록에서 인덱스에 해당하는 항목을 삭제하는 메서드의 형식.
  Object[] toArray(); // 목록에 저장된 항목들을 배열에 담아 리턴하는 메서드의 형식.
  int size(); // 목록에 저장된 항목의 개수를 리턴하는 메서드의 형식.
}
  • List 인터페이스는 인덱스를 기반으로 목록을 다루는 메서드의 규격을 정한다.
  • 인터페이스는 메소드를 Method signature형식으로만 적어준다.
  • 인터페이스의 메소드는 public을 안적어줘도 컴파일러가 알아서 선언해준다.
    → 다른 접근 지정자는 불가하다.

2단계 - List 규격에 따라 ObjectList를 구현한다.

  • ObjectList 클래스 변경
    • List 인터페이스를 구현한다. ⇒ implements 해준다.
    • List 인터페이스를 구현할거라고 선언해 놓고 List인터페이스 안에 있는 메소드들을 전부 구현해주지 않으면 컴파일 오류가 발생하게 된다. 혹은 추상 클래스가 된다.

인터페이스 규격에 따라 메서드를 정의하는 것 또한 오버라이딩으로 간주한다.

  • ObjectList class
package com.bitcamp.util;

//List  규격에 따라 메서드를 구현할 것이라고 선언한다!
// 만약 규격에 따라 메서드를 구현하지 않으면 컴파일을 안해준다!
public class ObjectList implements List {

  private static final int DEFAULT_CAPACITY = 10;

  private int size;
  private Object[] elementData;

  public ObjectList() {
    elementData = new Object[DEFAULT_CAPACITY];
  }

  public ObjectList(int initialCapacity) {
    elementData = new Object[initialCapacity];
  }

  @Override // 인터페이스 규격에 따라 메서드를 정의하는 것도 오버라이딩으로 간주한다.
  public void add(Object e) {
    if (size == elementData.length) {
      grow();
    }

    elementData[size++] = e;
  }

  @Override
  public Object[] toArray() {
    Object[] arr = new Object[size];
    for (int i = 0; i < arr.length; i++) {
      arr[i] = elementData[i];
    }
    return arr;
  }

  @Override
  public Object get(int index)  {
    if (index < 0 || index >= size) {
      throw new ListException("인덱스가 무효함!");
    }
    return elementData[index];
  }

  @Override
  public Object remove(int index) {
    if (index < 0 || index >= size) {
      throw new ListException("인덱스가 무효합니다!");
    }

    //삭제한 객체를 리턴할 수 있도록 임시 변수에 담아 둔다.
    Object deleted = elementData[index];

    for (int i = index + 1; i < size; i++) {
      elementData[i - 1] = elementData[i];
    }
    elementData[--size] = null;

    return deleted;
  }

  @Override
  public int size() {
    return size;
  }

  private void grow() {
    int newCapacity = elementData.length + (elementData.length >> 1);
    Object[] newArray = new Object[newCapacity];
    for (int i = 0; i < elementData.length; i++) {
      newArray[i] = elementData[i];
    }
    elementData = newArray;
  }
}

3단계 - XxxDao에서 의존객체를 가르킬 때는 클래스명 대신, 인터페이스 이름을 사용한다.

  • BoardDao 클래스 변경
    • ObjectList 객체의 주소를 담는 필드를 List 타입으로 변경한다.
    • List 규격에 따라 메서드를 호출한다.
  • MemberDao 클래스 변경
    • ObjectList 객체의 주소를 담는 필드를 List 타입으로 변경한다.
    • List 규격에 따라 메서드를 호출한다.
  • BoardDao class
    • List 인터페이스 레퍼런스인 list 변수는 List 규격에 따라 만든 객체 주소를 담을 수 있다.
    • 그런데 어찌됐든 list 레퍼런스의 데이터 타입은 List이므로 List에 없는 메소드는 호출할 수 없다.
package com.bitcamp.board.dao;

import com.bitcamp.board.domain.Board;
import com.bitcamp.util.List;
import com.bitcamp.util.ObjectList;

// 게시글 목록을 관리하는 역할
//
public class BoardDao {

  // List   인터페이스 레퍼런스인, list 변수는
  // List 규격에 따라 만든 객체 주소를 담을 수 있다.
  List  list = new ObjectList();

  private int boardNo = 0;


  public void insert(Object e) {


    Board board = (Board) e;
    board.no = nextNo();

    //List규격에 따라 만든 객체를 사용하여 목록에 추가한다.
    // => 메서드를 호출할 때는 List 규격에 따라 호출한다.
    list.add(e);
  }

  public Board findByNo(int boardNo) {

    for (int i = 0; i < list.size(); i++) {
      Board board = (Board) list.get(i);
      if (board.no == boardNo) {
        return board;
      }
    }

    return null;
  }

  public boolean delete(int boardNo) {

    // 의존 객체 ObjectList을 이용하여 목록에 저장된 게시글을 찾아 삭제한다.
    for (int i = 0; i < list.size(); i++) {
      Board board = (Board) list.get(i);
      if (board.no == boardNo) {
        return list.remove(i) != null;
      }
    }

    return false;
  }

  public Board[] findAll() {

    Object[] arr = list.toArray();

    Board[] boards = new Board[arr.length];

    for (int i = 0; i < arr.length; i++) {
      boards[i] = (Board) arr[i];
    }

    return boards;
  }

  private int nextNo() {
    return ++boardNo;
  }
}

024. List 규격에 맞춰 LinkedList 구현하고 적용하기

  • List규격에 맞추어 LinkedList 클래스를 만든다.
  • 기존의 ObjectList 대신 LinkedList 클래스로 교체한다.
  • 인터페이스를 도입했을 때 이점을 확인한다.

board-app project

1단계 - List 규격에 맞추어 LinkedList를 구현한다.

  • LinkedList 클래스 생성
    • 022 버전의 LinkedList 클래스를 가져온다.
    • List 인터페이스를 구현한다.
  • LinkedList class
    • LinkedList class도 ObjectList처럼 List 규격에 따라 만들어준다.
      ⇒ 즉, List 인터페이스에 선언되어 있는 모든 메소드들을 오버라이딩 해준다.
package com.bitcamp.util;

/**
 * Node를 이용해 값을 목록을 관리하는 일을 한다.
 * 
 * @author vivi
 *
 */
// LinkedList class도 ObjectList처럼 List 규격에 따라 만든다.
// 규격이 같으면 두 객체를 서로 교체할 수 있다. ObjectLsit <-> LinkedList class   
public class LinkedList implements List {

  private Node head; 
  private Node tail; 
  private int size; 


  @Override
  public void add(Object value) {
    Node node = new Node(value);

    size++; 

    if (tail == null) {
      head = tail = node; 
      return;
    }


    tail.next = node; 
    node.prev = tail;

    tail = node;
  }

  @Override
  public Object get(int index) {

    if (index < 0 || index >= size) {
      throw new ListException("인덱스의 범위를 초과했습니다!");
    }

    Node cursor = head;

    for (int i = 0; i < index; i++) {
      cursor = cursor.next;
    }

    return cursor.value;
  }

  @Override
  public Object remove(int index) {

    if (index < 0 || index >= size) {
      throw new ListException("인덱스의 범위를 초과했습니다!");
    }

    size--;

    Object deleted;

    if (head == tail) { 
      deleted = head.value; 
      head.value = null; 
      head = tail = null;
      return deleted; 
    }

    Node cursor = head;

    for (int i = 0; i < index; i++) {
      cursor = cursor.next;
    }

    if (cursor.prev != null) { 
      cursor.prev.next = cursor.next; 
    } else {
      head = cursor.next; 
      head.prev = null; 
    }

    if (cursor.next != null) { 
      cursor.next.prev = cursor.prev;
    } else { 
      tail = cursor.prev; 
      tail.next = null; 
    }

    deleted = cursor.value; 
    cursor.value = null;
    cursor.prev = null;
    cursor.next = null;

    return deleted; 
  }

  @Override
  public int size() {
    return size;
  }

  @Override
  public Object[] toArray() {
    
    Object[] arr = new Object[size];

    Node cursor = head;
    for (int i = 0; i < size; i++) {
      arr[i] = cursor.value;
      cursor = cursor.next;
    }

    return arr;
  }
}

2단계 - XxxDao에서 사용하는 ObjectList를 LinkedList로 교체한다.

  • BoardDao 클래스 변경
    • ObjectList 대신, LinkedList로 교체한다.
  • MemberDao 클래스 변경
    • ObjectList 대신, LinkedList로 교체한다.
  • 클래스 구조가 복잡해지지만, 유지보수는 쉬워진다.
    • ObjectList를 LinkedList로 바꾸기면 하면 끝난다.
  • List로 규격화된 클래스는 List로 선언이 가능하다.
  • 같은 규격으로 만들어진 클래스들 끼리는 서로 교체가 쉽다.
  • BoardDao class
package com.bitcamp.board.dao;

import com.bitcamp.board.domain.Board;
import com.bitcamp.util.LinkedList;
import com.bitcamp.util.List;

// 게시글 목록을 관리하는 역할
//
public class BoardDao {

  // List   인터페이스 레퍼런스인, list 변수는
  // List 규격에 따라 만든 객체 주소를 담을 수 있다.
  List  list = new LinkedList();

  private int boardNo = 0;


  public void insert(Object e) {


    Board board = (Board) e;
    board.no = nextNo();

    //List규격에 따라 만든 객체를 사용하여 목록에 추가한다.
    // => 메서드를 호출할 때는 List 규격에 따라 호출한다.
    list.add(e);
  }

  public Board findByNo(int boardNo) {

    for (int i = 0; i < list.size(); i++) {
      Board board = (Board) list.get(i);
      if (board.no == boardNo) {
        return board;
      }
    }

    return null;
  }

  public boolean delete(int boardNo) {

    for (int i = 0; i < list.size(); i++) {
      Board board = (Board) list.get(i);
      if (board.no == boardNo) {
        return list.remove(i) != null;
      }
    }

    return false;
  }

  public Board[] findAll() {

    Object[] arr = list.toArray();

    Board[] boards = new Board[arr.length];

    for (int i = 0; i < arr.length; i++) {
      boards[i] = (Board) arr[i];
    }

    return boards;
  }

  private int nextNo() {
    return ++boardNo;
  }
}



인터페이스 문법

  • 인터페이스는 규칙이기 때문에 구체적인 구현 내용이 없다.
  • 인터페이스에 선언될 수 있는 필드와 메서드들
    • 규칙은 공개되어야 하기 때문에 무조건 public이다.
      => private, protected, (default)는 없다.
    • method signature 형식으로 되어 있기 때문에 추상 메서드이다.
    • public, abstract 생략 가능하다.
  1. 상수 필드
(public static final) int TIMEOUT = 10000;
  1. 추상 메소드
public abstract void m1();
// public 을 생략할 수 있다.
abstract void m2(); // public 이 생략된 것이다. (default) 아니다!
// abstract 를 생략할 수 있다.
public void m3();
// public, abstract 모두 생략할 수 있다.
void m4();
  1. default 메소드
(public) default void call(){}
  1. static 메소드
  • 인터페이스에서 정의된 static 메서드는,
    • 인터페이스를 구현한 클래스를 통해 호출할 수 없다.
    • 구현체의 레퍼런스를 통해서도 접근할 수 없다.
(public) static void call(){}
MyInterface5Impl obj = new MyInterface5Impl(); // 인터페이스 구현체
MyInterface5Impl.m1(); // 컴파일 오류!, m1()은 static 메소드
obj.m1(); // 컴파일 오류!
  1. private 메소드
  • 쓰지 않는 것이 좋다.
private void call(){}

before(인터페이스 사용전)

  • 각 노동자에게 일을 시키는 방법이 다르다.
  • 왜?
    ⇒ 메서드 호출 방법이 다르기 때문에
  • 결론: 유사한 일을 하는데 객체 사용법이 다르다면 쓰기가 매우 불편하다.
  • 해결책: 유사한 일을 하는 객체에 대해 사용법을 통일하자.
  • 방법:
    1. 객체 사용 규칙을 정의한다.

    2. 그리고 클래스를 정의할 때 그 규칙에 따라 만든다.
      ⇒ 그러면 규칙에 따라 만든 클래스를 사용할 때는 일관된 방법으로 메서드를 호출할 수 있어 코딩하기가 훨씬 편해지고, 유지보수가 쉬워진다.

      이렇게 객체의 사용 규칙(호출 규칙)을 정의하는 문법이 "인터페이스(interface)"이다.

// Worker 구현체 사용
package com.eomcs.oop.ex09.a1.before;

public class Exam01 {

  public static void main(String[] args) {

    BlueWorker w1 = new BlueWorker();
    WhiteWorker w2 = new WhiteWorker();
    JubuWorker w3 = new JubuWorker();
    // 각 노동자에게 일을 시키는 방법이 다르다.
    // 왜? 
    // => 메서드 호출 방법이 다르기 때문에
    w1.doFight();
    w2.doZingZing();
    w3.doSsingSsing();
  }
}

after(인터페이스 사용 후)

  • 인터페이스(약속,규칙,규격,법률)를 이행하는 클래스는 인터페이스에 선언된 모든 메서드를 반드시 구현해야만 일반 클래스(concrete class)가 된다.
  • 만약에 구현하지 않으면, 추상 메서드인채로 존재한다.
  • 따라서 전부 구현하지 않으려면 추상 클래스로 변경해줘야 한다.
  • 인터페이스 레퍼런스?
Worker w;
  • 해당 인터페이스에 따라 작성된(사용규칙을 준수하는) 클래스의 인스턴스 주소를 저장한다.
  • 위 예제에서 w 레퍼런스를말로 표현하는 방법:
    1. Worker 사용규칙에 따라 작성된 클래스의 인스턴스 주소를 저장하는 변수 w.
    2. Worker 인터페이스를 구현한 클래스의 인스턴스 주소를 저장하는 변수 w.
    3. Worker 구현체의 인스턴스 주소를 저장하는 변수 w.
    4. Worker 구현체의 객체 주소를 저장하는 변수 w.
    5. Worker 구현 객체를 저장하는 변수 w.
    6. Worker 객체를 저장하는 (변수) w.
    7. Worker 객체를 가리키는 (변수) w.
    8. Worker 타입 객체 w.
  • 프로그램을 짜다가 인터페이스를 만나게 되면,
    • 누가 호출자이고 누가 피호출자인지 확인하라.
    • 본인이 맡은 개발 일이 호출자를 만드는 것인지, 아니면 피호출자를 만드는 것인지 확인하라.
  • worker Interface
package com.eomcs.oop.ex09.a1.after;

// caller(호출자;사용자) : Exam01
// callee(피호출자;도구) : BlueWorker, JubuWorker, WhiteWorker
public interface Worker {
  void execute();
}
  • BlueWorker()
// 구현체(implementor) - 인터페이스(사용규칙)에 따라 만든 클래스 
package com.eomcs.oop.ex09.a1.after;
public class BlueWorker implements Worker {@Override
  public void execute() {
    System.out.println("육체 노동자가 일을 합니다!");
  }
}
  • main()
// Worker 구현체 사용
package com.eomcs.oop.ex09.a1.after;
public class Exam01 {
  public static void main(String[] args) {
    Worker w1 = new BlueWorker();
    Worker w2 = new WhiteWorker();
    Worker w3 = new JubuWorker();

    w1.execute();
    w2.execute();
    w3.execute();

    // 역할?
    // => 사용 규칙: Worker
    // => caller : Exam01
    // => callee : BlueWorker, WhiteWorker, JubuWorker

  }
}

default 메서드

  • 초기에 인터페이스에는 m1(), m2() 메서드에 대한 규칙만이 있었는데, 시간이 흐르고 새로운 규칙인 m3()에 대한 규칙이 필요해서 추가하게 되었다.
  • 따라서 이전에 만들었던 클래스들인 Concrete1, 2 클래스들은 m1()과 m2() 메소드에 대한 정의만 해 놓은 상태이고, m3()에 대한 규칙이 만들어진 이후 만들어진 클래스인 Concrete3만 m3()에 대해 정의를 해 놓은 상태이다.
  • 이 경우, Concrete3 클래스는 문제가 되지 않지만, 나머지 클래스들은 모두 추상 클래스가 되어 버린다.
  • default메서드는 이러한 경우를 미연에 방지하고자 인터페이스에서부터 미리 정의를 대충 미리 해놓는 메서드이다.
  • 그래서 보통 인터페이스에서 default 메서드의 몸체는 비워져있다.
  • default 메서드는 오버라이딩을 해도 되고, 안해도 된다.

default 메서드는 과거에 작성한 코드의 영향을 주지 않으면서 새로운 규칙을 추가할 수 있게 해주는 문법이다!

인터페이스의 상속과 구현

  • 인터페이스도 다른 인터페이스를 상속 받을 수 있다.
  • 인터페이스를 구현할 때는 수퍼 인터페이스의 메서드까지 모두 구현해 줘야한다.
  1. 클래스의 레퍼런스 사용
    • 그 클래스에 정의된 메서드 호출 가능
    • 그 클래스의 상위 클래스에 정의된 메서드 호출 가능
  2. 인터페이스의 레퍼런스 사용
    • 인터페이스에 정의된 메서드 호출 가능
    • 상위 인터페이스에 정의된 메서드 호출 가능
  3. 수퍼 인터페이스의 레퍼런스 사용
    • 인터페이스에 정의된 메서드 호출 가능
    • 상위 인터페이스에 정의된 메서드 호출 가능
    • 구현체 클래스가 하위 인터페이스의 규칙에 따라 제작되었다면, 결국 그 수퍼 인터페이스의 규칙도 준수하는 것이 된다.
    • 따라서 수퍼 인터페이스 레퍼런스에 객체를 담을 수 있다.
      ⇒ 즉 수퍼 인터페이스 레퍼런스로 객체를 가리킬 수 있다.
  • 그러나 명심해야 할 점은, 레퍼런스 변수의 타입에 따라 가르킬 수 있는 범위가 달라진다는 것이다.
    • 해당 레퍼런스의 변수 타입에 존재하는 메서드만 가르킬 수 있다!
    • 만약 오버라이딩 된 메서드이고 현재 레퍼런스 변수 타입이 수퍼 클래스라면, 오버라이딩 된 메서드가 불러와 진다.

  • 그림만 봐도 인터페이스, 클래스를 구분할 수 있다!
    → 화살표로 구분한다. (점선: 클래스가 인터페이스 구현)
Myclass obj = new MyClass(); (;obj = 200, 인스턴스의 주소값)
obj.m1();
obj.rule1();
obj.rule2();

---

ProtocolB obj2 = obj; // ProtocolB의 규칙을 따라 만들었으므로 이와 같이 선언할 수 있다.
// (obj2 = obj = 200)
obj2.rule2();
obj2.rule1();
obj2.m1(); ← 컴파일 오류, m1()ProtocolB에 선언된 메서드가 아니다.
// 해당 레퍼런스 변수의 데이터 타입에 존재하지 않는 메소드는 호출할 수 없다.

---

ProtocolA obj3 = obj; // ProtocolA의 규칙을 따라 만들었으므로 이와 같이 선언할 수 있다.
// (obj3 = obj = 200)
obj2.rule1();
obj2.rule2(); ← 컴파일 오류, Protocol A의 메서드가 아니다!
obj2.m1(); ← 컴파일 오류, Protocol A의 메서드가 아니다!

다중 인터페이스 구현

  • 위의 그림의 MyClass에서 rule0(), rule1()는 ProtocolA의 입장에서 구현된 것이고,
    rule0(), rule2()은 ProtocolB의 입장에서 구현된 것이다.
  • 인터페이스 상에서는 메서드가 중복되더라도 상관이 없다.
    → 어차피 추상 메서드이기 때문에 어떤 인터페이스의 메서드를 상속 받더라도 결과는 같다.
  • 단, 두 메서드의 Method signature가 같아야 한다.
  • 즉, 두 메서드의 리턴타입과 파라미터 순서, 데이터 타입이 같아야 한다.
  • 리턴 타입만 다르면 다중 인터페이스 생성이 불가능하다.
    → 오버로딩이 불가능하기 때문이다.
    • 이 경우, 둘 중 어느 메소드를 상속 받느냐에 따라 동작이 다르다.
    • 메서드를 호출할 때 어떤 값을 리턴해야하는지 컴파일러가 헷갈려 할 것이다.
      ⇒ 따라서 리턴 타입만 다르면 다중 상속을 허용하지 않는다.
  • 파라미터 다르면 이름이 같아도 구현 가능하다.
  • default로 이미 정의되어 있는 같은 이름의 메서드들을 가진 인터페이스들도 다중 인터페이스 구현이 불가능하다.
    • 다만, defualt 메서드를 오버라이딩하면 어차피 인터페이스에서 구현한 default 메서드를 사용하지 않기 때문에 다중 구현이 가능해지다.
profile
https://github.com/Dingadung

0개의 댓글