본문 바로가기

WhiteShip Java Study : 자바 처음부터 멀리까지

인터페이스

선장님과 함께하는 자바 스터디입니다.

자바 스터디 Github

github.com/whiteship/live-study

 

whiteship/live-study

온라인 스터디. Contribute to whiteship/live-study development by creating an account on GitHub.

github.com

나의 Github

github.com/cmg1411/whiteShip_live_study

 

cmg1411/whiteShip_live_study

✍ 자바 스터디할래. Contribute to cmg1411/whiteShip_live_study development by creating an account on GitHub.

github.com

 

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

 

 

인터페이스

interface : 단어의 의미상 두가지 대상사이를 이어주는 면 인터 + 페이스 이다.

 

interface 키워드를 사용하여 클래스처럼 생성할 수 있다.

접근 제어자는 클래스와 마찬가지로 public, package-private 만 가능하다.

interface Runnable {

}

 

인터페이스는 일종의 설계도이다

설계도로써 다음의 의미를 가진다.

  • 상속의 is-a 관계와 달리 인터페이스는 has-a 관계이다.
  • 설계도를 제공함으로써 인터페이스를 구현하는 클래스를 만드는 개발자들의 개발 시간을 단축할 수 있다.
  • 클래스간 결합도를 낮출 수 있다.
  • 다형성을 이용하여 두 객체 사이를 연결해주는 중간 역할.
  • 사용자는 인터페이스를 사용하고, 각 개발자들은 인터페이스에 맞춰서 각자의 구현을 함으로써, 사용자의 코드를 고치지 않고 모듈을 갈아끼우는 식의 변경을 할 수 있다. -> 표준화 (ex. JDBC)
  • mix-in interface : 인터페이스는 다중 구현이 가능하기 때문에, 해당 클래스에 필요한 기능을 믹스인 하는 방식으로 추가할 수 있다.

 

설계도로써 다음의 특징을 가진다.

 

  • 인스턴스 변수는 선언할 수 없고, 상수만 선언 가능하다.
  • 추상 메서드만 사용 가능하다.
  • default 메서드를 사용할 수 있다. (JAVA 8 이후)
  • static 메서드를 사용할 수 있다. (JAVA 8 이후)

 

 

인터페이스의 정의

(접근지정자) interface (인터페이스명) {
    (public static final) 상수타입 상수이름 = 값;
    
    (public abstract) 메서드 시그니처;
}

인터페이스는 하위 클래스에서 접근하여 구현을 위한 타입이다 -> public 이어야 한다.

상수만 선언할 수 있다, -> static final

추상 메서드만 사용할 수 있다. -> abstract

 

 이기 때문에 public static final, public abstract 을 생략 가능하다.

 

 

인터페이스의 구현

추상 메서드의 extends 상속과는 다르게 implements-구현 키워드를 사용하여 하위 클래스를 구현한다.

public class 구현하는 클래스 implements 인터페이스명 {
	// 추상메서드를 반드시 오버라이딩해야함.
}

 

@Override

클래스를 상속할 때 상위 클래스의 메서드를 다시 정의하는 것도 오버라이딩이다.

인터페이스든, 추상 클래스든 추상메서드를 하위클래스에서 구현하는 것도 오버라이딩, 재정의이다.

 

이 오버라이딩에 @Override 어노테이션을 적으면 컴파일타임에 오버라이딩이 잘 됬는지 확인할 수 있다.

 

 

 

인터페이스 다중 상속

인터페이스는 클래스의 extends 와는 다르게 다중 상속이 가능하다.

public class Student implements Studiable, Runnable {
	// 두 인터페이스의 모든 추상 메서드를 오버라이딩 해야함
}

 

 

다중 상속의 주의점

1. 두 인터페이스에 이름과 파라미터가 같지만, 리턴 타입이 다른 메서드가 있다

-> 그 두 인터페이스를 한꺼번에 다중 상속할 수 없다.

 

public interface Readable {
    void print();
}
public interface Writeable {
    int print();
}
public interface File extends Readable, Writeable { // 불가능, 컴파일오류

}

 

 

2. 두 인터페이스가 이름, 파라미터, 리턴타입이 같은 추상 메서드를 구현

-> 그냥 오버라이딩 하면 된다.

public interface Readable {
    void print();
}
public interface Writeable {
    void print();
}
public interface File extends Readable, Writeable {
	@Override
    void print();
}

 

 

3. 한 인터페이스는 default 메서드, 한 인터페이스는 추상 메서드

-> 추상메서드를 오버라이딩해도 되고, default 메서드로 재정의해도 된다.

 

4. 두 인터페이스 모두 default 메서드

-> 오버라이딩해서 새로운 것을 작성해도 되고, 상위인터페이스.super.메서드() 로 상위 메서드 둘 중 하나를 호출해되 된다.

public interface Readable {
    default void print(){
        System.out.println("hi");
    }
}
public interface Writeable {
    default void print() {
        System.out.println("hello");
    }
}
public interface File extends Readable, Writeable {
    @Override
    default void print() {
        Readable.super.print();
        Writeable.super.print();
    }
}

 

5. 인터페이스를 상속받는 인터페이스는 extends 를 쓴다.

또한, 인터페이스는 다중 상속이 가능하다.

interface A {
}
interface C {
}
interface B extends A, C {
}

 

 

6. static 메서드는 오버라이딩할 수 없다. (정적 타이밍에 올라간 것이니 당연 .. )

 

 

 

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

public interface Readable {
    void read();
}

public interface Writeable {
    void write();
}

public interface Openable extends Readable, Writeable {
    void open();
}
public class File implements Openable {
    @Override
    public void open() {
        System.out.println("open");
    }

    @Override
    public void read() {
        System.out.println("read");
    }

    @Override
    public void write() {
        System.out.println("write");
    }
}

 

 

이런 구조의 인터페이스를 구현한 클래스 File 의 객체를 만들어 보자.

 

    public static void main(String[] args) {
        Readable readable = new File();
        // readable.open(); 불가능
        readable.read();
        // readable.write(); 불가능

        Writeable writeable = new File();
        // writeable.open(); 불가능
        // writeable.read(); 불가능
        writeable.write();

        Openable openable = new File();
        openable.open();
        openable.read();
        openable.write();

        File f = new File();
        f.open();
        f.read();
        f.write();
    }

 

상위 인터페이스 아무거나로 객체를 만들 수 있다.

하지만 어떤 인터페이스 타입으로 만드냐에 따라 사용 가능한 메서드가 다르다.

 

기본적으로 자바는 상위에서는 하위를 알 수 없다.

 

따라서 Readable 타입으로 만들면 Readable 인터페이스에 있는 메서드만 사용 가능한 식이다.

 

그리고 마지막을 보자. File 로 객체를 만들 수 있고, Openable 로 객체를 만들 수 있다.

두 객체 모두 모든 메서드를 사용 가능하지만,

 

Openable 을 상속받을 수 있는 새로운 Stream 클래스가 나왔다고 생각해보자.

class Stream implements Openable {
...
}

 

객체를 생성하기 위해서는 File 로 사용하고 있었더라면, 아래처럼 했어야 할 것이다.

Stream stream = new Stream();

 

하지만 인터페이스 레퍼런스로 객체를 만들고 있었더라면, 

Openable file = new Stream();

이렇게 뒷 부분만 바꿔주면 된다.

 

 

 

인터페이스 default 메서드

  • 인터페이스의 default 메서드는 JAVA 8 부터 생긴 기능이다.
  • 추상 클래스와 같이 인터페이스에서도 구현부를 작성할 수 있게 해준다.
  • 추상 메서드를 인터페이스에 추가하면, 구현하고 있는 모든 클래스에게 영향을 준다. 하지만 default 메서드는 모든 하위 클래스가 사용가능한 구현된 메서드이기 때문에 상관없이 추가할 수 있고, 하위 클래스들은 아무 제약이 없으므로 추가된지도 모를 수 있다.
  • Object 클래스의 메서드 (equals, hashCode, clone) 는 default 메서드로 오버라이딩 할 수 없다.
  • 인터페이스를 상속받은 인터페이스에서 다시 추상 메서드로 바꿀 수 있다.
  • default 메서드는 재정의할 수 있다.
interface A {
    default int sum(int a, int b) {
    	return a + b;
    }
}

위 A 인터페이스를 implements 한 모든 구현체는 sum 메서드를 가진다.

오버라이딩해도 상관없지만, 오버라이딩 하지 않으면 위 구현을 가진다.

 

또한 이미 개발이 진행된 인터페이스와 구현체들에 대해서도 인터페이스에 default 메서드를 넣어도 하위 구현체들이 꺠지지 않는다.

 

default 메서드의 출현 전에는?

  • default 메서드가 나오기 전에는 인터페이스에 구현부를 넣을 수 없었다.
  • 따라서 구현체들의 공통 구현을 중복적으로 구현해야 했다.
  • 이를 해결하기 위해 인터페이스를 구현하는 추상 클래스를 만들어 인터페이스의 추상 메서드를 구현하고, 그 추상메서드를 사용했다.
public interface Readable {
    void read();
    void print();
}
public abstract class AbstractReadable implements Readable {
    @Override
    public void print() {
        System.out.println("hi");
    }
}
public class ReadableImpl extends AbstractReadable {
    @Override
    public void read() {

    }
}

 

 

하지만 이런 방식으로 구현되지 않고 인터페이스만 사용하였다면, 공통부 구현을 처리할 수 없었고, 중간에 새로운 메서드 요구사항이 생기면 구현체 모두에 넣는 방법 밖에 없었다.

하지만 default 메서드가 나오고는 인터페이스에 구현부를 작성함으로 일이 많이 줄었다고 할 수 있다.

 

추가적으로 위와 같이 인터페이스 아래에 추상 클래스를 추상 골격 구현 클래스라 한다. 인터페이스의 추상 메서드와 디폴트 메서드로 구현하지 못한 나머지를 골격 구현 클래스에 구현한다.

또한 추상 골격 구현에서는 인터페이스의 추상메서드가 강제적으로 재정의해야 하는 것을 재정의 함으로써 아래의 구현체들은 필요한 메서드만 골라서 구현할 수 있다. 이를 템플릿 메서드 패턴이라 한다.

 

추상 클래스는 필요 없을까 ?

 

템플릿 메서드 패턴은 추상 클래스와 인터페이스의 장점을 모두  살린 패턴이다. default 메서드를 공부하다보면, 추상 클래스가 의미없어보이기도 한다. 하지만 추상 클래스와 인터페이스의 존재 목적은 다르다. 상속과 구현이 다른 것 처럼.

그리고 인터페이스에는 public 이외의 접근지정자는 안되는 것에 비해 추상 클래스에는 그런 제약이 없는 것 처럼 분명히 다른 점도 많다. 각자가 필요한 때가 다를 것이고, 그에 맞게 사용하면 된다.

 

default 메서드 주의점

하지만 이펙티브 자바 라는 책에서는 이미 구현되어 있는 인터페이스-구현체 구조에서는 default 메서드가 컴파일 타임에서는 문제없이 동작하지만, 런타임에서는 하위 클래스들과 문제가 생길 여지가 있다고 한다.

 

그래서 디폴트 메서드는 인터페이스로부터 메서드를 제거하거나 기존 메서드의 시그니처를 수정하는 용도가 아님을 명시하고, 되도록이면 기존 인터페이스에 디폴트 메서드를 추가하는 것 보다는, 애초에 설계할 때 디폴트 메서드를 이용한 설계를 세심히 진행하고 사용하라는 팁이 있다.

 

 

 

static 메서드

JAVA 8 버전 인터페이스에는 default 메서드말고도 static 메서드가 생겼다.

  • static 키워드를 가지고 구현부를 가진 정적인 메서드를 작성할 수 있다.
  • 인터페이스와 관련있는 정적인 메서드 (유틸성 메서드라든지) 를 작성하고 싶을 때 사용한다.
  • static 메서드는 구현체에서 재정의가 불가능하다.
interface A {
    static String getMessage(Exception e) {
        return e.getMessage();
    }
}

 

 

 

인터페이스의 private 메서드

  • 클래스에서 private 메서드는 외부에서 접근이 불가능한 특징으로, 주로 함수를 떼어내기 위해 내부에서만 사용되는 메서드를 작성하는데 쓰인다.
  • 코드의 공통 부분을 메서드로 만들어서 중복을 제거할 수 있다.

하지만 JAVA 8 버전 까지는 인터페이스에 public 접근지정자만 사용 가능했으므로, default 구문간의 공통 부분을 새로운 메서드로 빼지 못했다.

 

이를 해결하기 위해 JAVA 9 버전부터는 인터페이스에 private 메서드도 작성할 수 있게 되었다.

public interface A {
    default void morningGreeting() {
        introduceName();
        System.out.println("good morning !");
    }

    default void nightGreeting() {
        introduceName();
        System.out.println("good night !");
    }
    
    private void introduceName() {
        System.out.println("Hi, im tomas !");
    }
}

 

 

 

 

참고

www.notion.so/4b0cf3f6ff7549adb2951e27519fc0e6

www.notion.so/8-0cc8c251d5374ac882a4f22fa07c4e6a

ahnyezi.github.io/java/javastudy-8-interface/

강한결합 약한결합

 

'WhiteShip Java Study : 자바 처음부터 멀리까지' 카테고리의 다른 글

멀티스레드 프로그래밍  (0) 2021.02.06
예외 처리  (0) 2021.02.05
패키지  (0) 2021.02.02
디스패치, 다이나믹 디스패치, 더블 디스패치  (2) 2021.02.01
상속  (0) 2021.02.01