Java

8주차 - 인터페이스

voider 2021. 1. 8. 18:16

목표

자바의 인터페이스에 대해 학습하세요.

 

인터페이스 정의하는 방법

public interface Service {
    //final static 변수
    //default 메소드
    //public static 메소드
    public abstract void show();
}

인터페이스는 상수(final static), 추상 메소드, default메소드, public static메소드 이 네 가지만 가지고 있을 수 있다. 자바9부터는 private메소드도 선언할 수 있게 되었다. 인터페이스에 선언된 모든 변수는 상수, default, static, private이 붙지 않은 메소드는 추상 메소드다. 따라서 final static이나 public abstract키워드는 생략 가능하다. 컴파일러가 자동으로 붙여준다.

 

인터페이스 구현하는 방법

인터페이스 구현은 상속과 다르게 implements키워드를 사용하고, 다중 구현이 가능하다.

public class ServiceImpl implements Service {
    //Service인터페이스에 선언된 추상메소드 구현
    @Override
    public void show() {
        System.out.println("Hello world");
    }
}

인터페이스를 구현하는 클래스는 반드시 인터페이스에 선언된 추상메소드를 구현해야 한다.

 

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

인터페이스를 사용하는 가장 큰 목적이 다형성이다. 다형성은 요구사항 변경에 유연하게 대처할 수 있게 해준다. 다형성이란 동일한 역할을 수행한다면 얼마든 다른 객체로 대체할 수 있다는 뜻이다. 충전기를 예로 들어보자. 기존에 쓰던 충전기가 고장 나서 새로 샀는데 단자가 바뀌어서 핸드폰 충전 단자를 충전기에 맞게 수리해야 한다. 이건 좀 이상하다. 충전기가 고장났으면 충전기만 바꾸면 된다. 이게 다형성이다.

충전기를 바꾸면 핸드폰 충전 단자까지 수리해야 하는 코드

public class OldCharger {}
public class NewCharger {}

public class Phone {
    OldCharger cg;

    Phone(OldCharger cg) {
        this.cg = cg;
    }
}

이런 식으로 충전기를 사용한다면 충전기 클래스를 신상 충전기 클래스로 변경해야 하는 경우, 핸드폰 클래스까지 변경해야 한다.

인터페이스 레퍼런스 이용

public interface Charger {}
public class NewCharger implements Charger { }
public class OldCharger implements Charger { }
public class Phone {
    Charger cg;

    public Phone(Charger cg) {
        this.cg = cg;
    }
}

하지만 충전기를 인터페이스로 만들고 이것을 구현한 객체의 참조를 생성자로 받으면 핸드폰은 그게 어떤 충전기든 상관없이 충전할 수 있다. 인터페이스를 중간에 두어 결합도를 낮춰서 충전기의 변화가 핸드폰에 아무런 영향을 끼치지 않도록 할 수 있다. 이것이 인터페이스를 사용하는 목적이다.

 

인터페이스 상속

인터페이스도 상속이 가능하다. 인터페이스의 상속은 여느 상속과 같이 extends키워드를 사용한다. 또한 인터페이스는 클래스와 같이 Object 같은 기본으로 존재하는 상위 인터페이스가 없다.

  • 인터페이스는 인터페이스만 상속할 수 있다.
  • 다중상속 가능
interface Alphabet {}
interface Paper {]
interface Text extends Alphabet, Paper { }

 

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

인터페이스를 중간에 둠으로써 클래스와 클래스 사이를 느슨하게 연결할 수 있다는 장점이 있었다. 하지만 인터페이스 자체를 수정해야 하는 순간도 올 것이다. 만약 무선 충전 옵션을 추가해야 한다면 Charger인터페이스를 구현하는 모든 클래스를 수정해야 한다. 자바8은 이런 문제를 해결하기 위해 두 가지 해결책을 내놓았는데, 그 중에 하나가 default 메소드다. 인터페이스 내에서 메소드를 완성시켜두면 기존 클래스를 건드리지 않으면서 인터페이스를 변경할 수 있다. ChargerwirelessCharge() default 메소드를 추가하면 인터페이스를 구현하는 모든 클래스에 자동 상속된다.

interface Charger() {
    default wirelessCharge() {
        System.out.println("무선 충전");
    }
}

이렇게 default메소드를 추가하면 구현 클래스에 영향을 끼치지 않고 인터페이스를 변경할 수 있다.

 

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

static 메소드도 인터페이스에 만들 수 있다. static 메소드는 기존 static이 가지는 특성을 모두 가지고 있기 때문에 오버라이딩 할 수 없고 인터페이스 명으로 접근해야 한다. static 메소드를 이용해서 인터페이스를 구현하는 클래스에 영향을 끼치지 않고 변경하는 것도 가능하다. 하지만, static은 JVM 로딩 시 메모리에 올라가기 때문에 전역에서 공유해야 할 메소드가 아니라면 default 메소드를 사용하는 것이 낫다.

interface Charger() {
    static void print();
}

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

 

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

자바9에서는 인터페이스에서도 private 메소드를 사용할 수 있게 확장했다. 이것은 default 메소드를 사용할 수 있게 확장했던 것에 연장선인 것 같다. default 메소드를 정의할 수 있다는 것은 인터페이스 내에 어떤 역할을 가진 로직을 둘 수 있다는 뜻이다. 그러므로 그 로직에 이용되는 기능을 담을 private 메소드를 가질 수 있게 확장하는 것은 당연한 변화다. 추상화된 인터페이스만 제공하고, 구체 로직은 안으로 캡슐화하는 것이 객체지향의 기본 개념이기 때문이다.

public interface Charger {
    default void wirelessCharge() {
        cutWire();
        startCharge();
    }

    private void cutWire() { 로직.. }
    private void startCharge() { 로직.. }
}

당연히 static 메소드에서 사용할 수 없고 외부에 공개되지 않으므로 오버라이딩 될 수도 없다.