Java/STUDY HALLE

[Java] 인터페이스

무토(MUTO) 2021. 1. 8. 20:01

0. 학습목표

  • 인터페이스란 무엇인가?
  • 인터페이스를 정의하는 방법
  • 인터페이스를 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 Default 메소드(Java8)
  • 인터페이스의 static 메소드(Java8)
  • 인터페이스의 private 메소드(Java9)

1. 인터페이스란?

자바에서 개체를 표현할 때, 해당 개체가 외부와 통신하는데 필요한 규칙들의 모임.

내가 어느 세상의 신이고 생물체들을 빚어서 만들려고 한다고 가정해보자.
그런데 만들어야 할 생물이 너무 많아서 천사들에게 일을 위임하기 위해 필요한 부분들을 틀로 만들어서 제공하였다.
천사들은 내가 만든 비행의 기능을 가진 틀을 활용해서 생물체들을 만드는 작업에 착수하기 시작했다.
다음의 예시를 보자.

FlyingObject.java

package study.moon.test;

public interface FlyingObject {
    void fly();
}

Eagle.java

package study.moon.test;

public class Eagle implements FlyingObject{

    @Override
    public void fly() {
        flyWithBigWings();//구현방법은 어쨌든 상관없다. 날기만 하면 된다.
    }

    public void hunt() {
        ...
    }
    .
    .
    .
}

Bee.java

package study.moon.test;

public class Bee implements FlyingObject{

    @Override
    public void fly() {
        flyWithSmallWings();//구현방법은 어쨌든 상관없다. 날기만 하면 된다.
    }

    public void getHoney() {
        ...
    }
    .
    .
    .
}

나는 비행하는 생물들이 사용할 fly()라는 메소드를 FlyingObject 라는 인터페이스로 제공하였고,
천사들은 그것을 활용해서 각자 필요에 맞게 다양한 방법을 사용해서 생물체들에게 fly()를 구현하였다.

열심히 생명체들을 만들던 어느날, 유니콘이라는 말처럼 생겼으면서 날개를 가진 동물을 갑자기 만들고싶어졌다.
그런데 옛날에 만들어뒀던 인터페이스들이 생각이 나서 그것들을 활용하여 만들라고 천사들에게 지시했다.
천사들은 다음과 같이 새로운 생명체를 만들어냈다.

GroundObject.java

package study.moon.test;

public interface GroundObject {
    void run();
}

Unicorn.java

package study.moon.test;

public class Unicorn implements FlyingObject, GroundObject {

    @Override
    public void fly() {
        flyWithBirdWings();
    }

    @Override
    public void run() {
        runWithHorseLegs();
    }
}

천사들은 예전에 만들어뒀던 FlyingObject와 GroundObject를 활용하여 빠르게 유니콘을 만들어낼 수 있었다.

지금까지 보여주었던 예시들이 인터페이스를 사용하는 대표적인 이유이다. 해당 기능에 대한 틀을 제공하여 그것이 포함되어있다면 어떻게 동작하든 상관없이 같은 기능의 역할을 해낼 수 있도록 만든 장치이다. 또한 다중구현을 활용하여 여러개의 기능을 하나의 개체에 종합적으로 사용할 수 있게 만든 기능이기도 하다.

2. 인터페이스를 정의하는 방법

기본적인 인터페이스를 정의하는 방법은 다음과 같다.

(접근제어자) interface (이름) {

}

3. 인터페이스를 구현하는 방법

인터페이스를 구현하는 방법은 다음과 같다.

(접근제어자) class (이름) implements (인터페이스1, 인터페이스2, ...) {
    해당 인터페이스가 가지고있는 모든 메소드 오버라이딩
}

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

우리가 상속 시간에 공부했던 다이나믹 디스패치와 같은 방법으로 인터페이스를 사용할 수 있다.

package study.moon.test;

public class Main {

    public static void main(String[] args) {
        FlyingObject eagle = new Eagle();
        eagle.fly();
    }
}

다만 여기서 주의할 점은 어찌 되었든 현재 eagle의 상태는 FlyingObject이기 때문에 만약에 Eagle의 고유 메서드를 사용하고 싶다면 타입캐스팅을 통해서 해당 클래스의 메서드를 호출해야한다.

5. 인터페이스 상속

인터페이스끼리는 서로 상속을 할 수 있다. 자식인터페이스는 부모 인터페이스의 모든 것을 포함하며 구현체는 모든 인터페이스를 구현해야 한다. 예시는 다음과 같다.

MyObejct.java

package study.moon.test;

public interface MyObject {
    void move();
}

FlyingObject.java

public interface FlyingObject extends MyObject {
    void fly();
}

Eagle.java

package study.moon.test;

public class Eagle implements FlyingObject{

    @Override
    public void fly() {
        flyWithBigWings();
    }

    @Override
    public void move() {

    }

    public void hunt() {

    }
}

6. 인터페이스의 기본 메서드

만약 인터페이스에 새로운 메서드가 추가된다면 어떻게 될까?
자바 8이전의 인터페이스의 경우에는 굉장히 난감한 상황이 발생한다.
해당 인터페이스를 구현하는 모든 클래스에 추가적으로 메서드를 구현해주어야 하기 때문이다.
그래서 자바 8에서는 이러한 문제를 해결하기 위해서 디폴트 메서드라는 문법을 새로 만들어 메서드의 기본 구현을 가능하도록 만들어놓았다.

기본 메서드 선언 방법

package study.moon.test;

public interface GroundObject {
    void run();
    default void walk() {
        walkWithLegs();
    }

    private void walkWithLegs() {
        System.out.println("walkWithLegs");
    }
}

반환형 앞에 default라는 키워드를 붙이고 바디를 구현하면 기본 메서드의 선언이 이루어진다.

기본 메서드 활용 패턴

6-1. 선택형 메서드(비어있는 구현을 삭제)

인터페이스를 구현하는 클래스에서 메서드의 내용이 비어있는 상황을 본 적이 있을 것이다.
사용자가 해당 기능을 사용하지 않을 경우 해당 메서드의 구현을 비워두는 상황이 발생하곤 하는데 이 때, 기본 메서드를 활용하면 불필요한 코드의 낭비를 줄일 수 있다.

public class Bear implements GroundObject {

    @Override
    public void run() {
        blah();
        blahblah();
    }

    @Override
    public void dance() {
        //사람들이 사용하지 않지만 인터페이스를 반드시 구현해야하기 때문에 비워둔다.
    }
}

interface GroundObject {
    void run();
    void dance();
}

public class Bear implements GroundObject {

    @Override
    public void run() {
        blah();
        blahblah();
    }

// 디폴트메서드에 구현이 되어있기 때문에 해당 메서드에서 구현하지 않아도 된다!  
// @Override  
// public void dance() {  
//  
// }  
}
interface GroundObject {
    void run();
    default void dance() {

    }
}

6-2. 동작 다중상속

기본 메서드를 활용하면 기존에는 불가능했던 동작 다중상속 기능도 구현할 수 있다.

처음의 예시에서 나는 인터페이스의 형태만 제공하고 천사들에게 모든것을 일임했던 작업 형태에서
내가 인터페이스의 기본 예제까지 전부다 제공을 하고 필요하면 천사들이 오버라이딩 하여 변경해서 고쳐쓰는 형태로 작업을 할 수가 있게 되는 것이다.

6-3. 그러면 다중 상속 말고는 추상클래스랑 인터페이스를 구분해서 사용할 필요가 전혀 없는거 아닌가요?

그런것같습니다... 뭐가... 다른걸까요? 추상클래스를 사용해야 할 이유를 모르겠네...

6-4. 다중 상속에서의 메서드 우선순위

잠깐?! 다중상속에 구현이 된다구요? 그러면 다이아몬드 문제는 어떻게 해결하나요?

그렇다 자바 8에서는 디폴드 메서드가 추가되었기 때문에 같은 시그니쳐를 갖는 디폴트 메서드를 상속받는 상황이 발생가능하다.
그렇다면 자바 컴파일러는 어떻게 이 중복문제를 해결할까?

6-4-1. 클래스가 최우선적으로 호출된다.

package study.moon.test;

interface A {  
    default void hello() {  
        System.out.println("Hello I'm A");  
    }  
}

interface B extends A {  
    default void hello() {  
        System.out.println("Hello I'm B");  
    }  
}

class C implements A,B {

    public void hello() {
        System.out.println("Hello I'm C");
    }

    public static void main(String[] args) {
        new C().hello(); // 어떤것이 나올까요?
    }

}

interface A {  
    default void hello() {  
        System.out.println("Hello I'm A");  
    }  
}

interface B extends A {  
    default void hello() {  
        System.out.println("Hello I'm B");  
    }  
}

class D implements A {  
    public void hello() {  
        System.out.println("Hello I'm D");  
    }  
}

public class C extends D implements A,B {
    public static void main(String[] args) {
        new C().hello(); // 어떤것이 나올까요?
    }
}

6-4-2. 1번 상황을 제외하면 항상 서브 인터페이스가 호출된다.


package study.moon.test;

interface A {  
    default void hello() {  
        System.out.println("Hello I'm A");  
    }  
}

interface B extends A {  
    default void hello() {  
        System.out.println("Hello I'm B");  
    }  
}

class C implements A,B {  
    public static void main(String\[\] args) {  
        new C().hello(); // 어떤것이 나올까요?  
    }  
}

6-4-3. 1,2번 상황에서도 해결되지 않는다면 명시적으로 디폴트 메서드를 오버라이드하고 호출해야한다.(super인터페이스 사용)

package study.moon.test;

interface A {  
    default void hello() {  
        System.out.println("Hello I'm A");  
    }  
}

interface B {  
    default void hello() {  
        System.out.println("Hello I'm B");  
    }  
}

public class C implements A,B {
    public static void main(String[] args) {
        new C().hello(); // 어떤것이 나올까요?
    }

    @Override
    public void hello() {
        B.super.hello();// 명시적으로 B를 호출한다는것을 super인터페이스를 사용하여 보여줘야한다.
    }
}

7. 인터페이스의 static 메서드

static 키워드를 사용하면 import받지 않아도 전역적으로 해당 메서드를 사용할 수 있다.
동시에 default 키워드를 사용하는 것과 같이 구현을 제공할 수 있다.
그리고 해당 기능을 상속받은 하위 클래스에서 재정의 할 수 없다.

package study.moon.test;

public interface Car {

    default void ride(){
        System.out.println("Brrrrrr!!!!!");
    }
    static void booster() {
        System.out.println("Boooooooooossssterrrrrrr!!!!!!!!!!!!!!!!");
    }
}

package study.moon.test;

public class Main {
    public static void main(String[] args) {
        Car.booster();
    }
}

8. 인터페이스의 private 메서드

private 키워드를 사용하면 인터페이스 내부에서 default 메서드나 static 메서드에서 사용할 기능들을 구현할 수 있다.

package study.moon.test;

public interface GroundObject {  
    void run();

    default void walk() {
        walkWithLegs();
    }

    static void jump() {
        jumpWithLegs();
    }

    private void walkWithLegs() {
        System.out.println("walkWithLegs");
    }

    private static void jumpWithLegs() {
        System.out.println("jumpWithLegs");
    }
}  

'Java > STUDY HALLE' 카테고리의 다른 글

[Java] 쓰레드 Thread  (0) 2021.01.20
[Java] 예외처리  (0) 2021.01.15
[Java] 패키지  (0) 2020.12.29
[Java] 상속  (0) 2020.12.22
[Java] 클래스  (0) 2020.12.16