본문 바로가기

국비 교육

2020.9.3일자 수업 : 추상클래스, 인터페이스

 실습 - 상속 

 

상속 관계를 구현하는 프로세스

  • 전문화(Specialization) - 하나의 클래스에서 상속받아 추가된 기능들을 가진 클래스들을 다중으로 정읜한다.
  • 일반화(Generalization) - 다중 클래스들에서 공통점을 추출하여 하나의 클래스를 정의하고 이 클래스를 상속받게 한다.

일반화

클래스들의 공통 분모를 추출하여 수퍼 클래스를 정의하는 기법이다.

프로그래밍 처음부터 상속을 고려하여 수퍼 클래스를 정의하는 것이 아니라 코드를 리팩토링하는 과정에서 수퍼 클래스를 정의하는 것이기 때문에 초보 개발자에게 적합하다. 보통 일반화를 통해 추출된 수퍼 클래스는 서브 클래스에게 공통 분모를 상속해주는 것이 목적이므로 직접 수퍼클래스의 인스턴스를 생성하고 사용하지 않는다. 그래서 일반화를 통해 도출한 수퍼 클래스는 보통 추상 클래스로 정의한다.


의존성 주입(dependency injection)

한 객체가 의존하는 다른 객체를 내부에서 생성하지 않고 생성자를 통해 외부에서 주입 받음으로써 의존 객체 교체가 쉽도록 만드는 방법을 말한다. 이는 GoF에서 가장 유명한 전략 패턴을 프레임워크처럼 편리하게 사용할 수 있도록 만든것이라고 할 수 있다. 이것은 IOC와 같이 제어권이 개발자가 아닌 컨테이너에게 넘겨지게 되면서 가능하게 된 방식이다.

 

IOC(Inversion of Control)

IoC란Inversion of Control 제어의 역전이라는 뜻으로 메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라,

외부에서 결정되는 것을 의미한다.

 

제어의 역전 개념은 이미 폭넓게 적용되어 있다. 일반적으로 자바 프로그램은 main() 메소드에서 시작해서 개발자가 미리 정한 순서를 따라 객체가 생성되고 실행되지만 서블릿(자바를 사용하여 웹페이지를 동적으로 생성하는 서버측 프로그램 )과 같은 경우에는 프로그램을 개발하여 서버에 배포하고 나면 개발자는 서비스에 대한 제어 권한을 컨테이너(프로그래머가 작성한 코드를 스스로 참조하여 알아서 객체의 생성과 소멸을 컨트롤 해주는 기능)에게 넘기게 된다. 컨테이너는 적절한 시점에 서블릿 클래스의 객체를 만들고 그 안의 메서드를 호출한다. 개발자는 필요한 부분을 개발하여 끼워넣기의 형태로 개발하고 실행한다. 따라서 최종 호출은 개발자에 의해 제어되지 않고 프레임 워크 내부에서 결정된 대로 이뤄지게 되는데, 이것을 제어의 역전이라고 한다. 

 

(출처 : jobc.tistory.com/30)

 

훈련 목표

  • List라는 클래스를 만들어 ArrayList와 LinkedList에게 상속해주는 상속 관계를 맺는다.

  • ArrayList와 LinkedList의 공통된 멤버들을 List로 옮긴다.

  • Handler에서 필요한 List를 외부에서 주입받을 수 있도록 생성자를 수정한다.

1단계 : List를 만들고 ArrayList와 LinkedList의 공통 멤버들을 옮긴 다음, 각 클래스의 선언부에서 extends List를 추가한다. 공통된 메서드들 중 선언부만 같고 구현부가 다른 메서드들은 굳이 구현부까지 옮기지 않고, 선언부만 쓴다. 어차피 서브 클래스들의 인스턴스를 갖고 메서드를 호출하면 서브 클래스에 있는 메서드들이 호출될 것이기 때문이다.

package com.eomcs.util;

public class List<E> {
  protected int size = 0;
  
  public int size() {
    return this.size;
  }
  
  public boolean add(E e) {
    return false;
  }
  
  public void add(int index, E element) {

  }
  
  public E get(int index) {
    return null;
  }
  
  public E set(int index, E element) {
    return null;
  }
  
  public E remove(int index) {
    return null;
  }
  
  public Object[] toArray() {
    return null;
  }
  
  public E[] toArray(E[] arr) {
    return null;
  }
}

구현부가 비어있는 메서드들을 작성하는 이유

기존의 서브 클래스에 있던 메서드들은 수퍼클래스를 오버라이딩한 것이 된다. 따라서 호출해도 슈퍼클래스의 메서드가 호출될 일은 없다.
그럼에도 불구하고 슈퍼클래스에서 메서드를 작성하는 것은 서브 클래스들이 갖춰야할 기능을 정의한다는 면에서 의미가 있다. 

 

2단계 : Handler의 필드 boardList, memberList....들의 데이터 타입을 List로 통일시킨 후, 생성자를 만들어 이 필드에 들어갈 객체를 외부에서 주입받도록 한다.

 List<Board> boardList;
  
  public BoardHandler(List<Board> list) {
    this.boardList = list;
  }

 

1) 다형적 변수의 활용

목록을 다루는데 필요한 의존 객체를 특정 클래스로 제한하지 말고 상위 클래스의 레퍼런스를 사용하여 여러개의 서브 클래스를 사용할 수 있도록 유연성을 제공하자.

List<Board> boardList;

 

2) 의존 객체 주입 활용

의존 객체를 이 클래스에서 직접 생성하지 말고 외부로부터 주입받는다. 기본값 또는 의존 객체를 준비할 수 있게 하는 생성자를 활용한다.

public BoardHandler(List<Board> list) {
    this.boardList = list;
  }

 

다형적 변수와 의존 객체 주입을 통한 효과

외부에서 언제든지 객체를 만들때마다 그에 필요한 객체의 종류를 선택해서 생성할 수 있다.

BoardHandler boardHandler = new BoardHandler(new ArrayList<Board>());

BoardHandler boardHanler = new BoardHandler(new LinkedList<Board>());

 


 실습 2 - 추상 클래스 

 

추상 클래스

 

- 서브 클래스에 기본 기능 및 공통 분모를 상속해 주는 역할을 하는 클래스다.

- new 명령을 통해 인스턴스를 생성할 수 없다.

- **상속** 의 기법 중에서 **일반화** 를 통해 수퍼 클래스를 정의한 경우 보통 추상 클래스로 선언한다.

- 추상 메서드를 가질 수 있다.

 

List와 같은 클래스들은 다른 클래스에서 상속받아 쓰려고만든 클래스이다. 그런데 누군가가 이 List의 인스턴스를 만드는 것을 막지 못한다. 이를 막기 위해 추상클래스 abstract 문법을 만들었다. 우리가 일반적으로 사용했던 클래스들은 구체적인 클래스 Concrete Class였고, 이에 반해 인스턴스를 만들지 못하는 클래스를 추상적인 클래스 Abstract Class라고 한다. 추상클래스는 인스턴스는 만들지 못하지만 레퍼런스 변수는 만들 수 있기 때문에 다형적 변수를 통해서 그를 상속받는 다양한 클래스를 다룰 수 있다.

 

추상 메서드

 

서브 클래스에 따라 구현 방법이 다른 경우 보통 추상 메서드로 선언한다. 추상 메서드는 서브 클래스에서 반드시 구현해야 하는 메서드다. 서브 클래스를 정의할 때 반드시 해당 메서드를 구현하도록 강제하고 싶다면 추상 메서드로 선언한다. 추상 메서드는 추상 클래스만이 가질 수 있다.

 

훈련 목표

  • List를 추상클래스로 변경한다.

  • List에서 구현부가 없는 메서드들을 추상메서드로 변경한다.

1단계 : List 클래스 선언부에 abstract를 추가하고 구현부가 없는 모든 메서드들의 선언부에도 abstract를 추가한다. size와 같이 모든 하위 클래스에서 구현부까지 공유하고 있는 메서드는 추상 메서드로 만들면 안된다. 

package com.eomcs.util;

public abstract class List<E> {
  protected int size = 0;
  
  public int size() {
    return this.size;
  }
  
  public abstract boolean add(E e);
  
  public abstract void add(int index, E element);
  
  public abstract E get(int index);
  
  public abstract E set(int index, E element);
  
  public abstract E remove(int index);
  
  public abstract Object[] toArray();
  
  public abstract E[] toArray(E[] arr);
}

2단계 : List 클래스가 추상 클래스임을 확연히 알 수 있도록 이름을 abstractList로 변경한다.

public abstract class AbstractList<E>

 추상클래스 

git/eomcs-java-basic/src/main/java com.eomcs.oop.ex08

상속의 종류

  • specialization

    가장 많이 사용하는 방법으로 수퍼 클래스를 상속 받아 서브 클래스를 만드는 것이다.

    수퍼클래스에 새 특징을 추가하거나 새 기능을 추가하여 더 특별한 일을 수행하는 서브클래스를 만든다.

    그래서 이런 상속을 "특수화/전문화(specialization)"이라 부른다.

  • generalization

    리팩토링 과정에 수행하는 방법이다.

    서브클래스들의 공통 분모를 추출하여 수퍼클래스를 정의하는 방법을 말한다.

    그래서 이런 상속을 "일반화/표준화(generalization)"이라 부른다.

Sedan과 Truck은 공통된 메서드 start(), shutdown(), run()이 있다. 

public class Sedan {
  public void start() {
    System.out.println("시동 건다!");
  }
  public void shutdown() {
    System.out.println("시동 끈다!");
  }
  public void run() {
    System.out.println("쌩쌩 달린다.");
  }
  public void doSunroof(boolean open) {
    if (open) {
      System.out.println("썬루프를 연다.");
    } else {
      System.out.println("썬루프를 닫는다.");
    }
  }
}

public class Truck {
  public void start() {
    System.out.println("시동 건다!");
  }
  public void shutdown() {
    System.out.println("시동 끈다!");
  }
  public void run() {
    System.out.println("덜컹 덜컹 달린다.");
  }
  public void dump() {
    System.out.println("짐을 내린다.");
  }
}

이 두 클래스를 일반화하여 Car클래스를 만들고 이를 상속받게 한다. run()은 서브 클래스에 따라 재정의할 필요가 있기 때문에 각 서브 클래스에서 오버라이딩할 수 있도록 한다.

public class Sedan extends Car {
 
  @Override
  public void run() {
    System.out.println("쌩쌩 달린다.");
  }
  public void doSunroof(boolean open) {
    if (open) {
      System.out.println("썬루프를 연다.");
    } else {
      System.out.println("썬루프를 닫는다.");
    }
  }
}

public class Truck extends Car {
  
  @Override
  public void run() {
    System.out.println("덜컹 덜컹 달린다.");
  }
  public void dump() {
    System.out.println("짐을 내린다.");
  }
}

public class Car {
  
  public void start() {
    System.out.println("시동 건다!");
  }
  public void shutdown() {
    System.out.println("시동 끈다!");
  }
  
  public void run() {
    System.out.println("달린다!");
  }
}

추상클래스

Car 클래스의 인스턴스를 만들어 사용하는 것을 막기 위해 추상 클래스라는 개념을 사용한다. Car 클래스를 추상 클래스로 바꾸는 순간 Car의 인스턴스를 사용한 코드에서 컴파일 오류가 뜬다.

public abstract class Car {
}

public class Exam01 {

  public static void main(String[] args) {
    Sedan s = new Sedan();
    Truck t = new Truck();
    // Car c = new Car(); // 컴파일 오류!
  }
}

추상 메서드

서브 클래스에서 재정의할 메서드라면 굳이 수퍼 클래스에서 구현할 필요가 없다. 추상클래스에서 이렇게 구현할 필요가 없는 메서드를 굳이 구현하지 않고 선언부만 남겨놓은 메서드추상 메서드라고 한다.

 

추상 메서드를 선언하면 서브 클래스가 구현하도록 강제할 수 있는 기능도 있다. 추상 클래스를 상속 받는 서브클래스는 추상 메서드를 반드시 구현해야 한다. 만약 구현하지 않으면 서브클래스도 추상클래스가 되어야 한다.

 

왜 추상 클래스만이 추상 메서드를 갖는가?

추상 메서드가 있다는 것은 해당 메서드를 실행할 수 없다는 것이고 실행할 수 없는 메서드를 갖는 클래스 인스턴스를 생성해서는 안되기 때문이다. 일반 클래스는 인스턴스를 생성하여 메서드를 호출하기 때문에 구현되지 않은 메서드를 가지는 것은 오류이다.

 

추상 메서드는 선언부에 abstract를 추가하고 구현부를 생략한다. 추상 클래스가 아니면 추상 메서드를 작성할 수 없다.

package com.eomcs.oop.ex08.d2;

public abstract class Car {
  public void start() {
    System.out.println("시동 건다!");
  }
  
  public void shutdown() {
    System.out.println("시동 끈다!");
  }
  
  public abstract void run();
}

슈퍼클래스에 있는 추상 메서드를 서브 클래스에서 구현하지 않으면 컴파일 오류가 뜬다.

package com.eomcs.oop.ex08.d2;

public class Sedan extends Car {
  
//  @Override
//  public void run() {
//    System.out.println("쌩썡 달린다.");
//  }
// -> 구현하지 않으면 컴파일 에러가 뜬다.

  public void doSunroof(boolean open) {
    if (open) {
      System.out.println("썬루프를 연다.");
    } else {
      System.out.println("썬루프를 닫는다.");
    }
  }
}

 추상 클래스 / 추상 메서드의 문법

git/eomcs-java-basic/src/main/java com.eomcs.oop.ex10.a

 

  • 클래스 앞에 abstract를 붙인다.
public abstract class A {

}

 

  • 메서드 앞에 abstract를 붙인다.
public abstract void m1();

 

 

  • 추상 메서드는 구현될 수 없다.
public abstract void m2() {} // 컴파일 오류!

 

  • 오직 추상 클래스만이 추상 메서드를 가질 수 있다.
public class A3 { // 컴파일 오류!
  public abstract void m1();
}

public abstract class A4 {
  public abstract void m3();
}

 

  • 추상 클래스가 아니라면 추상 메서드를 가지면 안된다.
public abstract class A4 {
  public abstract void m3();
}

public class Exam02 extends A4 { // 컴파일 에러!
}

public class Exam03 extends A4 {
  @Override
  public void m3() {}
}

 

  • 추상 클래스는 인스턴스를 생성할 수 없다.
public Test {
  static public abstract A {

  }

  public static void main(String[] args) {
    A obj = new A(); // 컴파일 오류!
  }
}

 


 템플릿 메서드 

git/eomcs-java-basic/src/main/java com.eomcs.oop.ex10.c.d

템플릿 메서드 패턴(디자인 패턴)

알고리즘의 구조를 메소드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의 하는 패턴이다. 알고리즘이 단계별로 나누어 지거나, 같은 역할을 하는 메소드이지만 여러곳에서 다른형태로 사용이 필요한 경우 유용한 패턴이다.

(출처 : https://yaboong.github.io/design-pattern/2018/09/27/template-method-pattern/)

 

슈퍼 클래스(Student)에서 전체적인 논리 흐름(introduce)을 정의하고, 서브 클래스(Kim, Hong)에서 구체적인 동작(greet, cheerup)을 정의하는 방식

 

public abstract Student {
  public introduce() {
    greet();
    cheerup();

  }

  public abstract void greet();
  public abstract void cheerup();
}

public Kim extends Student {

  public void greet() {
    System.out.println("안녕하세요!");
  }
  
  public void cheerup() {
    System.out.println("잘해봅시다!");
  }
}

public Hong extends Student {

  public void greet() {
    System.out.println("안녕하십니까. 잘 부탁드립니다.");
  }
  
  public void cheerup() {
    System.out.println("아자아자!");
  }
}

public Test {
  public static void main(String[] args) {
    Kim s1 = new Kim();
    Hong s3 = new Hont();
    
    s1.introduce();
    System.out.println("---------------------");
    s2.introduce();
  }
}

 


 Interface

git/eomcs-java-basic/src/main/java com.eomcs.oop.ex9.a

인터페이스는 비슷한 기능을 하는 객체들을 사용하거나 이 객체들의 메서드를 호출할 때 일관성을 제공하기 위해 만들어진 호출 규칙이다.

 

예를 들어, 다음과 같이 비슷한 일을 하는 객체의 메서드를 호출하려면 각 객체의 메서드 이름을 확인하고 호출해야한다. 이러한 객체가 더 늘어나면 일일이 이것을 확인하기가 더욱 까다로워진다

public class BlueWorker {

  public void doFight() {
    System.out.println("육체 노동자가 일을 합니다!");
  }
}

public class JubuWorker {
  
  public void doSsingSsing() {
    System.out.println("주부로 일합니다.");
  }
}

public class WhiteWorker {
  
  public void doZingZing() {
    System.out.println("사무직 노동자가 일을 합니다.");
  }
}

package com.eomcs.oop.ex09.a.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();
  }
}

 

따라서 메서드를 호출하는, 혹은 클래스를 사용하는 객체, 즉 caller의 입장에서 일관적으로 객체들을 사용하기 위해서는 하나의 통일된 호출 규칙을 가져야한다. 그리고 호출, 사용되는 객체, 즉 callee들을 이 규칙에 따라 일관적으로 설계해야한다. 이 규칙을 인터페이스라고 한다.

 

이렇게 하면 일관된 방법으로 메서드를 호출할 수 있어 코딩하기가 훨씬 편해지고 유지보수가 쉬워진다.

 

다음은 WhiteWorker, JubuWorker, BlueWorker들에 대한 인터페이스이다. 인터페이스는 메서드의 몸체는 정의하지 않는다. 메서드의 몸체는 이 인터페이스를 구현하는 객체안에서 정의된다.

// caller(호출자;사용자) : Exam01
// callee(피호출자;도구) : BlueWorker, JubuWorker, WhiteWorker
// 문법:
//    interface 사용규칙명 {...}
//
public interface Worker {
  void execute();
}


다음은 Worker라는 인터페이스를 구현한 WhiteWorker 클래스이다. 구현 객체, 즉 호출 규칙을 이행, 실행하는 객체는 반드시 선언부에서 implement (구현한 인터페이스)를 붙여야 한다. 또한 해당 인터페이스에서 정의된 모든 메서드들을 구현해야한다. 하나라도 구현하지 않는다면 추상 메서드로 남겨져 해당 클래스는 일반 클래스가 되지 못하도 추상 클래스가 된다.

public class WhiteWorker implements Worker {
  @Override
  public void execute() {
    System.out.println("사무직 노동자가 일을 합니다.");
  }
}

 인터페이스 문법 

git/eomcs-java-basic/src/main/java com.eomcs.oop.ex9.b
  • 인터페이스에 정의된 메서드는 호출 규칙으로 늘 공개되어야한다. 따라서 인터페이스에 선언되는 모든 메서드는 Public이다.
  • 무조건 public이기 때문에 public을 생략해도 된다. 따라서 접근제어자가 없는 메서드는 default가 아니라 public을 뜻한다.
  • 그 밖에 다른 접근제어자를 명시하면 컴파일 오류가 뜬다.
public interface A {
  public void m1();
  
  void m2();
  
  private void m3(); // 컴파일 오류
  protected void m4(); // 컴파일 오류
}

 

  • 인터페이스에 선언되는 모든 메서드는 몸체를 구현하지 않는다. 구현하면 컴파일 오류가 뜬다.
  • 따라서 모든 메서드가 추상 메서드이다.
  • 모든 메서드가 추상 메서드이기 때문에 abstract를 생략할 수 있다.
public interface A2 {
  //void m1() {} // 구현하면, 컴파일 오류!

  abstract void m2();  

  void m4();
}

따라서 인터페이스에 선언되는 메서드는 모두 public abstract이고 이것은 언제든지 생략이 가능하다.

 

인터페이스는 규칙이므로 new 연산자로 인스턴스를 생성할 수가 없다. 따라서 인터페이스에 선언되는 모든 변수는 static 변수가 될 수밖에 없다. 규칙은 변경되어서는 안되기 때문에 final로 선언되고, 공개되어야하기 때문에 접근 범위가 public이다. 따라서 인터페이스에 선언되는 모든 변수는 public static final이다. 이것을 모두 생략하는 것도 가능하다. 스태틱 블록은 둘 수 없다.

public interface A4 {
  
  // static {}

  public static final int v1 = 100;
  /*public static final*/ int v2 = 200;
}

 

인터페이스의 모든 멤버는 public이고 이를 구현하는 메서드이것보다 접근 범위를 좁힐 수 없다. 

public class Exam02 implements A3 {

  //private void m1() {}  // 컴파일 오류!
  //protected void m1() {} // 컴파일 오류!
  //void m1() {} // 컴파일 오류!
  //void m2() {} // 컴파일 오류!
  public void m1() {}
  public void m2() {}
}

 인터페이스와 상속

git/eomcs-java-basic/src/main/java com.eomcs.oop.ex9.c

인터페이스끼리는 상속 관계를 맺을 수 있다. 일반 클래스 관계와 마찬가지로 extends (슈퍼클래스) 를 선언부에 추가하기만 하면 상속 관계를 맺을 수 있다. 슈퍼 인터페이스를 상속받은 서브 인터페이스는 슈퍼 인터페이스에서 선언된 메서드를 모두 갖는다. 이 서브 인터페이스를 한 일반 클래스가 구현하려면 해당 인터페이스는 물론, 그 인터페이스의 슈퍼 인터페이스까지 구현해야한다.

public interface A {
  void m1();
}

public interface B extends A{
  void m2();
}

public interface C {
  void m3();
}

public interface D {
  void m2();
  void m4();
}

public interface D2 {
  int m3();
}

위와 같은 인터페이스들이 있다고 할 때 B를 구현하는 일반 클래스는 B 뿐만 아니라 A에 있는 메서드들도 모두 구현해야한다.

public class Exam01 implements B {
  
  // 인터페이스에 선언된 메서드 구현
  public void m2() {} // B 인터페이스 뿐만아니라,
  public void m1() {} // B의 수퍼인터페이스의 메서드까지 구현해야 한다.
}

또한 레퍼런스 변수의 타입이 어떤 인터페이스인지에 따라서 호출할 수 있는 메서드도 달라진다. 

  • 인스턴스와 같은 일반 클래스 타입 - 클래스 안에서 정의된 모든 메서드
  • 인터페이스 - 해당 인터페이스와 그 상위 인터페이스에서 선언된 모든 메서드
public class Exam01 implements B {
  

  public void m2() {} // B 인터페이스의 메서드
  public void m1() {} // A 인터페이스의 메서드
  
  // 클래스에서 추가한 메서드
  public void x1() {} 
  public void x2() {}
  
  public static void main(String[] args) {
    
    Exam01 obj = new Exam01();
    
    // 클래스 타입 레퍼런스는 해당 클래스에 정의된 메서드를 호출할 수 있다.
    obj.m1();
    obj.m2();
    obj.x1();
    obj.x2();
    
    // 인터페이스 레퍼런스는 해당 인터페이스에 선언된 메서드만 호출할 수 있다.
    B obj2 = obj;
    obj2.m2(); // B.m2()
    obj2.m1(); // A.m1() <== B가 상속 받은 인터페이스의 메서드
    //obj2.x1(); // 컴파일 오류!
    //obj2.x2(); // 컴파일 오류!
    
    A obj3 = obj;
    obj3.m1(); // A.m1()
    //obj3.m2(); // 컴파일 오류!
    //obj3.x1(); // 컴파일 오류!
    //obj3.x2(); // 컴파일 오류!
    
  }
}

클래스는 인터페이스를 다중으로 구현할 수 있다. 다만 자신이 구현하기로한 모든 인터페이스의 메서드들을 모두 구현해야한다. 만약 위에 정의된 A, B, C, D, D2  인터페이스가 있다고 가정했을 때, 한 클래스가 B와 C를 구현한다면 B와 C, 그리고 B가 상속받은 A 인터페이스의 모든 메서드를 구현해야한다. 마찬가지로, 레퍼런스 변수의 타입에 따라 호출할 수 있는 메서드는 다르게 한정된다. 예를 들어 B 타입의 레퍼런스 변수에 저장하면 C 인터페이스에서 정의된 메서드는 호출하지 못한다. 

public class Exam02 implements B, C {
  public void m2() {} // B 인터페이스 뿐만아니라,
  public void m1() {} // B의 수퍼인터페이스의 메서드까지 구현해야 한다.
  public void m3() {} // C의 인터페이스 구현 
  
  public static void main(String[] args) {
    
    Exam02 obj = new Exam02();
   
    B obj2 = obj;
    obj2.m2(); // B.m2()
    obj2.m1(); // A.m1() <--- 상속 받은 메서드
    //obj2.m3(); // 컴파일 오류!
    
    C obj3 = obj;
    obj3.m3(); // C.m3()
    //obj3.m1(); // 컴파일 오류!
    //obj3.m2(); // 컴파일 오류!
  }
}

같은 이름과 같은 파라미터 타입을 갖는 중복된 메서드가 상속받은 여러개의 인터페이스에 있을 수 있다. 이와 같은 경우에는 문제가 없다. 어차피  인터페이스 내에서는 실제로 구현된 메서드가 아니므로, 두 인터페이스에서 요구하는 메서드를 만든 것으로 둘 다 인정이 되기 떄문이다. 한 메서드를 여러 인터페이스가 공유하고 있는 셈이다. 따라서 양측의 타입인 레퍼런스 변수에 저장하여 메서드를 호출하는 것이 모두 가능하다.

public class Exam03 implements B, C, D {
  public void m1() {} // B의 수퍼인터페이스인 A 인터페이스 구현
  public void m2() {} // B와 D 인터페이스 구현
  public void m3() {} // C의 인터페이스 구현 
  public void m4() {} // D의 인터페이스 구현
  
  public static void main(String[] args) {
    Exam03 obj = new Exam03();
    
    B obj2 = obj;
    obj2.m2(); // 여기에서는 B.m2() 이다.
    obj2.m1();
    
    C obj3 = obj;
    obj3.m3();
    
    D obj4 = obj;
    obj4.m2(); // 여기에서는 D.m2() 이다. 같은 메서드를 여러 인터페이스에서 공유할 수 있다.
    obj4.m4();
    
  }
}

 

다만 양측의 인터페이스가 메서드가 갖고 있는 메서드 명과 파라미터는 같지만 리턴 타입이 다른 경우이다. 이 경우, 일반 클래스에서는 파라미터 형식도 같고 다만 리턴 타입이 다른 메서드를 여러개 정의할 수가 없으므로 불가능한 것이다.

package com.eomcs.oop.ex09.c;

public class Exam04 implements C, D2 {

  // 다음과 같이 정의하면 컴파일 오류이다!
  public void m3() {} // C의 인터페이스 구현 
  public int m3() {} // D2의 인터페이스 구현 

}