본문 바로가기

Java

[WS live-study] 8주차: 인터페이스

학습할 것

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

인터페이스 (interface)

지난 포스팅에서 클래스는 상속(extends)이 1개만 가능하다고 했다. 이건 객체지향 프로그래밍에서의 큰 제약사항이다. 그래서 이 점을 해결하기 위해 인터페이스가 존재한다. 인터페이스는 이를 구현(implement)하는 클래스가 인터페이스의 타입이나 메서드를 사용할 수 있도록 제공한다.

 

같은 인터페이스를 구현한 클래스는 인터페이스 타입으로 호환이 가능하다. 따라서 인터페이스 타입 변수가 이를 구현한 한 클래스의 객체를 담고 있다가 중간에 다른 클래스의 객체이더라도 같은 인터페이스를 구현하였다면 대체가 가능하다. 이 점은 클래스의 상속과 같다. 하지만 중요하게 다른 점이 있다. 인터페이스는 여러 인터페이스를 한 클래스에서 구현이 가능하다.

 

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

인터페이스 선언은 추상클래스와 크게 다르지 않다. 인터페이스의 메서드(default 메서드 제외)는 추상 메서드이고 class라는 키워드 대신 interface로 선언을 하면 된다.

예를 들어보겠다.

interface Centered {
    void setCenter(double x, double y);
    double getCenterX();
    double getCenterY();
}

이 인터페이스의 제약사항은 다음과 같다.

  • 모든 인터페이스의 메서드는 추상적이다. 선언부만 존재하며 abstract 수식어를 붙여도 되지만 붙이지 않아도 생략되어 있다.
  • 인터페이스는 public 접근제어자이다. 이를 붙이지 않아도 관례상 이 수식어가 생략되어 있다.
  • 인터페이스의 필드는 상수(final static) 여야 한다.
  • 인터페이스는 생성자가 존재하지 않는다. --> 생성자가 존재하지 않는다는 건 객체를 생성할 수 없다는 것을 의미한다. 다형성의 타입으로서 변수타입을 지정할 수는 있으나 new를 이용한 새로운 객체생성은 안된다.
  • java 8버전부터 static 메서드가 포함될 수 있다. (이전 버전 X) - 이후 설명
  • java 9버전부터 private 메서드가 포함될 수 있다. (이전 버전 X) - 이후 설명

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

어떠한 클래스를 상속 받는다는 의미로 'extends 클래스명'을 선언하듯이, 인터페이스의 경우에는 'implements 인터페이스명'을 사용한다. 다중 인터페이스를 구현할 때는 다음과 같이 콤마로 구분한다.

class 클래스명 implements 인터페이스명, 인터페이스명 {}

위와 같이 클래스가 인터페이스를 구현하겠다고 선언했다면 이 인터페이스가 가지고 있는 추상 메소드의 구현부를 의무적으로 작성해야한다. 

아래 예제를 보면 Center의 메서드들을 구현클래스인 Rectangle이 모두 구현한 것을 확인할 수 있다.

 

interface Centered {
    void setCenter(double x, double y);
    double getCenterX();
    double getCenterY();
}

class Rectangle implements Centered{
// 클래스 필드
    private double w, h;
    private double rx, ry;

    public Rectangle(double w, double h) {
        this.w = w; this.h = h;
    }
    public double getWidth() { return w; }
    public double getHeight() { return h; }


// ** 인터페이스 메서드 구현 **
    public void setCenter(double x, double y) {
        this.rx = x;
        this.ry = y;
    }

    public double getCenterX() {
        return rx;
    }

    public double getCenterY() {
        return ry;
    }
}

 

만약 인터페이스의 메서드를 모두 구현할 수 없는 상황이라면 클래스에 abstract 키워드를 추가하고, 당장 구현하지 않는 메서드에도 abstract 키워드를 추가하여 이 클래스를 상속받는 클래스가 구현할 수 있도록 해야한다.

 

// ** 클래스에 abstract 키워드 추가 **
abstract class Rectangle implements Centered{
    protected double w, h;

    public Rectangle(double w, double h) {
        this.w = w; this.h = h;
    }
    public double getWidth() { return w; }
    public double getHeight() { return h; }
	
// ** 메소드에 abstract 키워드 추가 **
    public abstract void setCenter(double x, double y);
    public abstract double getCenterX();
    public abstract double getCenterY();
}

class CenteredRectangle extends Rectangle {
    private double cx, cy;

    CenteredRectangle(double cx, double cy, double w, double h) {
        super(w, h);
        this.cx = cx;
        this.cy = cy;
    }


// ** 상속받은 추상클래스를 모두 구현해야한다. **
    public void setCenter(double x, double y) {
        cx = x;
        cy = y;
    }

    public double getCenterX() {
        return cx;
    }

    public double getCenterY() {
        return cy;
    }
}

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

아래의 예제와 같이 구현을 하였다면 Centered 타입의 변수에 같은 인터페이스를 구현한 다른 클래스의 객체들로 값을 대체할 수 있다.

interface Centered {
    void setCenter(double x, double y);
    double getCenterX();
    double getCenterY();
}

class CenteredRectangle implements Centered {
    private double cx, cy;

    CenteredRectangle(double cx, double cy) {
        this.cx = cx;
        this.cy = cy;
    }

    // 인터페이스 메서드 구현
    public void setCenter(double x, double y) {
        cx = x;
        cy = y;
    }

    public double getCenterX() {
        return cx;
    }

    public double getCenterY() {
        return cy;
    }
}

class CenteredCircle implements Centered {
    /*
        ...
    */
}

class CenteredSquare implements Centered {
    /*
        ...
    */
}

 

위와 같이 구현을 하였다고 가정했을 때 아래와 같이 같은 인터페이스 타입으로 여러 클래스의 객체를 저장하여 사용할 수 있다.

public static void main(String[] args) {
    Centered centered = new CenteredCircle(1.0, 1.0);
    System.out.format("x: %.1f, y: %.1f \n", centered.getCenterX(), centered.getCenterY());
    /*
        [출력 결과]
        x: 1.0, y: 1.0
    */

    centered = new CenteredSquare(2.5, 2);
    System.out.format("x: %.1f, y: %.1f \n", centered.getCenterX(), centered.getCenterY());
    /*
        [출력 결과]
        x: 2.5, y: 2.0
    */

    centered = new CenteredRectangle(2.3, 4.5);
    System.out.format("x: %.1f, y: %.1f \n", centered.getCenterX(), centered.getCenterY());
    /*
        [출력 결과]
        x: 2.3, y: 4.5
    */
}

 

또는 아래와 같이 메서드 파라미터로 다른 타입의 변수를 받는 것도 가능하다.

class Shape {
    private Centered centeredShape;

    public void setCenteredShape(Centered centeredShape) {
        this.centeredShape = centeredShape;
    }

    public Centered getCenteredShape() {
        return this.centeredShape;
    }
}

 

아래와 같이 다른 클래스의 객체를 넘겨주는 방법으로 사용하는 것을 확인할 수 있다.

    Shape shape = new Shape();
    shape.setCenteredShape(new CenteredCircle(1.0, 1.0));
    shape.setCenteredShape(new CenteredSquare(2.5, 2));
    shape.setCenteredShape(new CenteredRectangle(2.3, 4.5));

인터페이스 상속

클래스의 상속처럼 인터페이스도 인터페이스끼리 extends 키워드를 사용하여 상속이 가능하다.

클래스 상속과 같이 부모 인터페이스의 메서드와 상수를 상속받게 되고, 자식 인터페이스에서 새로운 상수와 메서드를 가질 수 있다.

다만 클래스 상속과 다른점은 인터페이스의 상속은 다중 상속이 가능하다.

 

interface Positionable extends Centered {
    void setUpperRightCorner(double x, double y);
    double getUpperRightX();
    double getUpperRightY();
}

// 다중 상속이 가능하다.
interface Transformable extends Scalable, Translatable, Rotatable { }
interface SuperShape extends Positionable, Transformable { }

 

하나 이상의 인터페이스를 상속받은 인터페이스는 상속받은 각각의 인터페이스들의 모든 메서드와 상수를 상속받는다. 

그리고 다중 상속 받은 인터페이스를 구현하는 클래스는 구현할 인터페이스가 가지고 있는 추상메서드와 상속받은 인터페이스들의 메서드까지 모두 구현부를 작성해야 한다.

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

지금까지 설명한 인터페이스의 메서드는 추상메서드의 성격을 가졌다. 하지만 인터페이스에도 클래스 안의 메서드처럼 메서드 구현까지 정의할 수도 있다. 그것이 바로 default 메서드이며, java 8버전 이상부터 사용할 수 있다.

 

public interface Dog {
    // default 메서드 선언. 구현부까지 모두 작성.
    default void cry() {
        System.out.println("mung..");
    }
}

// Dog 인터페이스 구현
class Poodle implements Dog {}
class Bulldok implements Dog {}

class Main {
    public static void main(String[] args) {
        Poodle poodle = new Poodle();
        poodle.cry(); // --> 출력결과 : mung..
    }
}

이제까지는 인터페이스에서 선언부만 정의되어 있는 메서드만 작성을 하고 구현클래스에서 인터페이스의 메서드의 구현부를 작성하였지만 위의 예시에서 확인할 수 있듯이 인터페이스에서 default 메서드를 작성하면 일반 클래스가 상속받듯이 구현클래스의 객체가 인터페이스의 default 메서드를 호출하고 있는 것을 확인할 수 있다.

인터페이스의 Static 메서드, 자바 8

자바 8버전부터는 static 메서드를 인터페이스에서도 선언할 수 있다. static 메서드는 상속이 불가능하다.

 

public interface Dog {
    default void cry() {
        System.out.println("mung..");
    }
    
    // static 메서드 선언
    static int lifeExpectancy() {
        return 15;
    }
}

class Poodle implements Dog {}
class Bulldok implements Dog {}

class Main {
    public static void main(String[] args) {
        Poodle poodle = new Poodle();
        poodle.cry();
        
        // 인터페이스 static 메서드 호출
        System.out.println(Dog.lifeExpectancy()); // --> 출력결과 : 15
    }
}

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

자바 9버전부터 인터페이스에서 private 메서드 선언도 가능하다. private 접근제어자이기 때문에 상속되지 않고, 구현클래스가 당연히 이 메서드의 구현부를 작성할 수 없다. 따라서 선언과 동시에 구현부도 모두 작성을 해야하고, 외부에서 호출이 되지 않고 인터페이스 내부에서 사용가능하다.

public interface Dog {
    // private 메서드 선언
    private void bark() {
        System.out.println("wang!!");
    }

    default void cry() {
        System.out.println("mung..");

        // 인터페이스 내 private 메서드 호출
        bark();
    }

    static int lifeExpectancy() {
        return 15;
    }
}

 

 

 


정리하는데 도움을 준 자료들

[책] Java in a Nutshell

'Java' 카테고리의 다른 글

컬렉션을 쓰는 이유  (0) 2021.05.27
equals와 hashcode는 왜쓰는거지?  (0) 2021.05.25
[WS live-study] 7주차: 패키지  (0) 2021.02.07
[WS live-study] 6주차: 상속  (0) 2021.02.04
[WS live-study] 5주차: 클래스  (0) 2021.02.03