8주차 항해일지 - 인터페이스

김종하·2021년 1월 6일
0

Live Study - WHITESHIP

목록 보기
9/14
post-thumbnail

목차
0. 들어가기전..
1. 인터페이스 정의하는 방법
2. 인터페이스 구현하는 방법
3. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
4. 인터페이스 상속
5. 인터페이스의 기본 메소드 (Default Method), 자바 8
6. 인터페이스의 static 메소드, 자바 8
7. 인터페이스의 private 메소드, 자바 9

  • 추가 - 함수형 인터페이스 , 추상클래스와 인터페이스

0. 들어가기전..

인터페이스(Interface) 는 컴퓨터 공학에서 굉장히 많이 사용되는 단어이다.
서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면을 의미한다. 그리고 JAVA 에서 인터페이스는 구현이 없는 추상메소드와 상수로만 이루어져 있는 ( 1.8 버전 부터는 하위호완성을 위해 등장한 default 메소드가 있지만, 잠시 잊고 이 부분은 밑에서 살펴보도록 하겠다) 특이한 구조를 가지고 있다.

그렇다. 시스템간의 접점을 위해서는 '무엇인가' 를 '어떤식으로 사용하자'는 '약속'이 필요하다.
그리고 이 '무엇인가'는 A 시스템을 동작시키도록 구현되어 있거나 B 시스템을 동작시키도록 구현된게 아닌 추상적인 개념일 뿐이고, A 와 B 가 그것을 구현하는 것이다.

한글로 숫자 '일'을 '일' 이라고 쓸 수 있고, 영어로 숫자 '일'은 'ONE' 이라고 쓴다.
서로가 서로의 언어를 가려쳐 줄 수 없는 상황이라면, 혹은 서로의 언어를 가르치는데 드는 비용이 너무 큰 상황이라면, 그 다음 대안은 특정 모양이 '일' 그리고 'ONE' 이다 라고 약속하는 방법이 있을 것이다. '1' 이라는 것을 '일' 그리고 'ONE'으로 사용하자는 약속을 통해 한글과 영어간의 접점이 이루어질 수 있는 것이다.

아마, 수학이 지금까지 발전하는데 있어서 가장 큰 역할을 한 사람은 유명한 수학자들이 있기에 앞서서, 아라비아 숫자라는 굉장히 직관적으로 잘 설계된 인터페이스를 개발한 고대의 누군가가 아닐까?

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

interface 는 class 와 동일한 레벨을 가진다고 할 수 있을 것 같다.
다만, 클래스와 달리 일반 메소드나 생성자는 가질 수 없고 추상메소드와 상수만 가질 수 있었다.

가장 기본적인 형태의 인터페이스는 다음과 같다.

public interface Tv {}

추상메소드 와 상수를 추가해 보겠다.

public interface Tv {

    int size = 100;

    void turnOnTv();

    void turnOffTv();

}

기본적으로 인터페이스의 모든 멤버변수는 상수이기 때문에 public static final 을 붙이지 않아도 상수라 가정하고, 모든 메소드가 추상메소드임으로 abstract 키워드를 생략해도 추상메소드라 가정한다.

참고로 인터페이스의 상수는 정적영역(static block)에서 초기화 할수 없기 때문에 반드시 선언과 동시에 초기화 해야 한다.

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

인터페이스는 추상적인 개념이다. 당연히 생성자도 없고, 생성자가 없으니 인스턴스를 만들 수 없다.
구현체 클래스를 사용해 인터페이스의 추상적인 개념을 구체화하고 인스턴스화 할 수 있다.

public class SamsungTv implements Tv {}

다음과 같이 implemets 라는 키워드를 통해 해당 인터페이스를 구현하는 클래스임을 선언할 수 있다. 단, 여기까지만 하면 컴파일 에러가 발생한다. 인터페이스에서 정의한 모든 메소드들은 구현체에서 오버라이딩해 구체화 하여야 하기 때문이다. 물론 인터페이스를 구현한 클래스는 인터페이스가 강제화하는 메소드를 정의해야된다는 것 이외에는 일반적인 클래스와 완전히 동일하다

public class SamsungTv implements Tv {
    @Override
    public void turnOnTv() {
        System.out.println("삼성Tv 전원 키기");
    }

    @Override
    public void turnOffTv() {
	System.out.println("삼성Tv 전원 끄기");
    }
    
    // interface 에서 강제한 메소드 뿐만 아니라, 다른 메소드들 정의하는 것도 당연히 가능
    public void samsungService(){
        System.out.println("삼성 서비스");
    }
}

public class LgTv implements Tv{
    @Override
    public void turnOnTv() {
        System.out.println("엘지tv 전원 킴");
    }

    @Override
    public void turnOffTv() {
        System.out.println("엘지tv 전원 끔");
    }
}

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

앞서 말했든 인터페이스는 클래스 레벨이다. 즉, 클래스 처럼 reference type 으로 사용할 수 있다.

즉 다음과 같이 사용가능하다는 것이다. 물론, Tv 타입임으로 samsungService()메소드는 사용할 수 없다.

이를활용 하면 다음과 같이 사용할 수도 있다.

public static void main(String[] args) {

        Tv samsungTv = new SamsungTv();

        Tv lgTv = new LgTv();

        ArrayList<Tv> tvList = new ArrayList<>();
        tvList.add(samsungTv);
        tvList.add(lgTv);

        SamsungTv sTv = (SamsungTv) tvList.get(0);
        sTv.samsungService();
        
    }

ArrayList 에는 동일한 타입의 데이터 집합임으로, 만약
ArrayList<SamsungTv> 와 같이 사용했다면 lgTv 는 추가하지 못했을 것이다. 하지만 Tv 타입을 사용함으로써 SamsungTv 와 LgTv 모두 ArrayList 의 데이터로 사용할 수 있었다. 이러한 특징은 클래스의 상속과 동일하지만, 인터페이스는 클래스와는 또 다른 특성을 가지고 있다. 다음 챕터에서 알아보도록 하자.

4. 인터페이스 상속

자바에서 클래스는 다중상속이 불가능하다. 즉,

public class A extends B, C{
} 

이런식으로 사용하는 것은 불가능 하다는 것이다.

하지만 인터페이스는 다중상속이 가능하다. 그리고 다중상속을 한 클래스는 상속한 인터페이스들의 추상메소드들을 모두 재정의 하여야 한다.

다음 코드를 살펴보며 인터페이스의 다중상속에 대해 알아보자.

// '카메라' 인터페이스 생성
public interface Camera {

    void takePhoto();

}
// '폰' 인터페이스 생성 
public interface Phone {

    void call();

}

// '카메라'와 'TV', '폰' 을 상속하는 JadenPhone 클래스 생성 
public class JadenPhone implements Phone, Tv, Camera {
    @Override
    public void takePhoto() {
        System.out.println("사진 찰칵");
    }

    @Override
    public void call() {
        System.out.println("전화 따르릉");
    }

    @Override
    public void turnOnTv() {
        System.out.println("티비 시청");
    }

    @Override
    public void turnOffTv() {
        System.out.println("티비 종료");
    }
}

// '카메라'와 'TV' 를 상속하는 JadenPad 클래스 생성 
public class JadenPad implements Tv, Camera{
    @Override
    public void takePhoto() {
        System.out.println("사진 찰칵");
    }

    @Override
    public void turnOnTv() {
        System.out.println("티비 시청");
    }

    @Override
    public void turnOffTv() {
        System.out.println("티비 종료");
    }
}

이처럼 다중상속을 활용해 유연한 코딩이 가능해진다. 또한 3 챕터에서 살펴보았듯이 인터페이스 래퍼런스를 통해 구현체를 사용하는 것이 가능한데 JadenPhone 클래스의 경우 Phone, Camera, Tv 타입이 될 수 있다. 그리고 이러한 특성을 다형성 (Polymorphism) 이라한다

물론, Phone 타입으로 한다면 Phone 이 정의한 call() 메소드만 사용가능하다.

5. 인터페이스의 기본 메소드 (Default Method), 자바 8

자바8 이 등장하면서 인터페이스는 기본 메소드(Default Method) 를 사용할 수 있게 되었다.
기존에는 인터페이스에서 정의한 모든 메소드들은 해당 인터페이스를 구현한 구현체에서 모두 Overriding 해서 사용해야 했지만, 기본 메소드는 반드시 구현할 필요가 없다.

이러한 기본 메소드의 등장배경에는 바로 하위호완성이 있다. 기존에 있던 인터페이스에 반드시 추가해야할 사항이 생겼는데, 이미 해당 인터페이스를 구현한 구현체가 엄청나게 많다고 생각해보자. 기존의 구현체들은 해당 사항을 특별히 필요로 하지도 않는데, 추가된 사항으로 인해 모두 깨져버릴 것이다. 이 때문에, 필요로 하는 구현체에서만 사용할 수 있도록 기본 메소드가 생겨났다.

public interface Camera {

    void takePhoto();

    void takeVideo(); // Camera 에 takeVideo()를 추가 

}


카메라에 takeVideo()를 추가했더니, 카메라를 구현하고 있던 구현체들이 다 깨져린다.
이때 default method 를 활용한다면 이런 상황을 피해갈 수 있다. 또한 default method 는 구현부를 가질 수 있음으로, overriding 이 필요하지 않으면 그대로 사용할 수 있다.

// JadenPhone 에 takeVideo()를 재정의하지 않았음에도
        JadenPhone jadenPhone1 = new JadenPhone();
        jadenPhone1.takeVideo();  // --> 비디오 촬영 

다중상속으로 인한 기본메소드 충돌 해결

앞서 살펴보았듯이 인터페이스에서 다중상속이 가능하다. 자바8 이전에는 여러 인터페이스에서 같은 메소드를 가지더라도 어짜피 구현체에서 오버라이딩하게 됨으로 아무런 문제가 없었지만, 기본 메소드의 등장으로 인터페이스들이 각각 동일한 기본 메소드를 가지고, 클래스에서 충돌이 발생하는 메서드를 명시적으로 오버라이딩 하지 않으면 컴파일러가 어떤 기본 메서드를 사용해야할 지 선택할 수 없기 때문에 문제가 발생할 수 있다. 이를 해결하기 위해선 다중상속을 한 클래스에서 직접 오버라이딩 하던가, 가능하다면 인터페이스들을 상속관계에 둠으로써 (상속한 인터페이스의 기본메소드가 사용된다) 해결할 수 있다.

6. 인터페이스의 static 메소드, 자바 8

static 메소드는 인스턴스를 만들어 사용하는 것이 아님으로, 기술적으로 인터페이스에서 static 메소드를 사용하는 것은 가능했지만, 인터페이스의 추상성을 해친다는 이유로 제약을 걸어왔다고 한다. 하지만 자바 8부터는 개발의 유연성을 위해 이러한 제약을 풀고, static 메소드를 사용할 수 있게 하였다

public interface Camera {

    void takePhoto();

    default void takeVideo(){
        System.out.println("비디오 촬영");
    }
    
    static void cameraInfo(){
        System.out.println("카메라다");
    }
   

}

public static void main(String[] args) {

        Camera.cameraInfo(); // --> 카메라다
}

7. 인터페이스의 private 메소드, 자바 9

자바9 부터는 인터페이스에서 private 키워드를 붙인 메소드를 사용할 수 있게 되어 default 메소드나 static 메소드의 구현부를 작성할 때 중복을 최소화힐 수 있게 하였다

public interface Camera {

    void takePhoto();

    default void takeVideo(){
        System.out.println("비디오 촬영");
    }
    
    static void cameraInfo(){
        showInfo();
    }

    private static void showInfo(){
        System.out.println("카메라다");
    }
   

}

public static void main(String[] args) {

        Camera.cameraInfo(); // --> 카메라다
}

추가 - 함수형 인터페이스

이 포스팅은 백기선님의 더 자바, Java8 강의를 참고하여 포스팅 하였습니다.

함수형 인터페이스?

추상메소드를 하나만 가지고 있는 인터페이스

@FunctionalInterface
public interface FuncInterface {

    void doSomething();

}

@FunctionalInterface 어노테이션을 통해 이 인터페이스가 함수형 인터페이스임을 직관적으로 보여주고, 추상메소드가 두 개 이상인 경우 에러를 보여줌으로써 안전한 코딩을 가능하게 해준다.

람다 표현식

함수형 프로그래밍에서는 람다 표현식을 통해 기존에 anonymouse innerclass 로 구현해야 했던 코드를 간단하게 줄일 수 있게 되었다.

FuncInterface funcInterface = new FuncInterface() {
            @Override
            public void doSomething() {
                System.out.println("do something");
            }
        };
        
 ------ 
 람다 표현식을 사용하면 
 
 FuncInterface funcInterface = () -> System.out.println("do something");

자바에서 함수형 프로그래밍은 비록 함수의 형태이지만, anonymouse innerclass 가 사용되는 것을 봤듯이 특수한 형태의 객체이고, first-class-object 로써 변수에 할당하고, parameter 로 전달하고 return 객체로 사용 하는게 가능하다.

또한 함수형 프로그래밍에는 제약사항이 있는데, 순수함수여야 한다는 것이다. 순수함수라 함은 동일한 input 이 들어가면 항상 같은 output 을 return 해야한다는 것인데 , 이를 위해선 상태를 가지지 않아야한다.

자바가 제공해주는 함수형 인터페이스

java.util.function 에서 자주 사용할 만한 함수형인터페이스들을 제공해 주고 있다.

제공해주는 함수형 인터페이스를 한번 사용해보자.

public class Plus10 implements Function<Integer, Integer> {

    @Override
    public Integer apply(Integer integer) {
        return integer + 10;
    }
}

 public static void main(String[] args) {

        Plus10 plus10 = new Plus10();
        System.out.println(plus10.apply(10)); // --> 20 
        
}

이외에도 많은 인터페이스들이 제공되니, 래퍼런스를 참고해 사용해보면 좋을 것 같다.

추가 - 추상클래스와 인터페이스

인터페이스로 추상클래스를 완전히 대체할 수 있는가?

default 메소드와 static 메소드를 인터페이스에 사용할 수 있게 되면서, 추상클래스의 역할(?) 의 많은 부분을 인터페이스로 대체할 수 있게 되었다. 하지만 그렇다고 해서 추상클래스의 모든 것을 대체할 수 있는 것은 아니다.
우선, 기본적으로 인터페이스는 상태값을 가질 수 없다.

public interface SampleInterface {

    default void greeting(){
        System.out.println("Hello, interface");
    }

}

public abstract class SampleAbstractclass {

    public String message = "Hello, this is Abstract Class";

    public SampleAbstractclass() {
    }

    public SampleAbstractclass(String message) {
        this.message = message;
    }

    void greeting(){
        System.out.println(message);
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

    public static void main(String[] args) {
        SampleAbstractclass sample = new SampleAbstractclass("HI, THIS IS ABSTRACT CLASS") {
            @Override
            void greeting() {
                super.greeting();
            }
        };
        sample.greeting();
    }

인터페이스는 상수값만 가질 수 있기 때문에 추상클래스처럼 상태값을 사용해 코드를 조작하기는 힘들다. 이처럼, 추상클래스의 많은 부분을 인터페이스가 대체할 수 있게 되었지만 아직까지 추상클래스 역시 존재의 이유가 있다.

스터디 깃헙주소 : https://github.com/whiteship/live-study/issues/8

예제코드 깃헙레포 : https://github.com/JadenKim940105/whiteship-study/tree/master/src/main/java/weeks8

0개의 댓글