티스토리 뷰

Java/개념정리

[Java] 인터페이스(interface)

통통푸딩 2021. 10. 6. 00:34
반응형

인터페이스란?

인터페이스는 객체와 객체 사이에서 상호작용의 매개로 쓰이는데, 일종의 추상클래스이다.

그러나 추상클래스보다 추상화 정도가 높아서 일반 메소드나 멤버변수를 구성원으로 가질 수 없다.

추상 메소드와 상수만 구성원으로 가질 수 있다.

( ⚠ 자바 8 이후부터는 default 일반 메소드 와 static 메소드를 구현할 수 있다.)

 

 

1. 인터페이스 정의

인터페이스의 선언은 예약어로 class가 아닌 interface 키워드를 사용한다.

접근 제어자는 public 이나 default를 사용한다.

public interface 인터페이스명 {
    //1) 변수(상수)
    타입 상수명 = 값;

    //2) 추상 메소드
    타입 메소드명(매개변수, ...);

    // 디폴트 메소드
    default 타입 메소드명(매개변수, ...){
        // 구현부
    }

    // 정적 메소드
    static 타입 메소드명(매개변수, ...){
        // 구현부
    }
}

1) 변수(상수) : 인터페이스 내에 존재하는 변수는 무조건 "public static final"로 선언되며, 이를 생략할 수 있다.

 

2) 추상 메소드 : 인터페이스 내에 존재하는 메소드는 무조건 "public abstract"로 선언되며, 이를 생략할 수 있다.

강제적으로 오버라이딩하여 재구현해서 사용해야한다.

 

3) default 메소드 : 인터페이스에서 기본적으로 제공하지만 구현내용을 재구현하고 싶다면 오버라이딩이 선택적으로 가능하다.

 

4) static 메소드 : 인터페이스에서 제공하는 것으로 무조건 사용해야한다.



 


2. 인터페이스 구현

인터페이스는 키워드 implements 를 사용하여 구현한다.

 

Hero.java (인터페이스)

public interface Hero {
    int MAX_HP = 100;

    void attack();
    void heal(int portion);

    default void sound() {

    }
}



 

Hulk.java (구현 클래스)

public class Hulk implements Hero {
    private int hp = Hero.MAX_HP;

    @Override
    public void attack() {
        System.out.println("눈에 보이는 것을 다 던진다.");
    }

    @Override
    public void heal(int portion) {        
        hp += portion;
        if(hp > Hero.MAX_HP)
            hp = Hero.MAX_HP;

        System.out.println("헐크가 체력을 " + portion + "만큼 회복했습니다.");
    }

    @Override
    public void sound(){
        System.out.println("크아아아아!!!!");
    }

    public void jump() {
        System.out.println("헐크가 엄청 높게 점프합니다.");
    }
}



 

CaptainAmerica.java (구현 클래스)

public class CaptainAmerica implements Hero {
    private int hp = Hero.MAX_HP;

    @Override
    public void attack() {
        System.out.println("방패를 던진다.");
    }

    @Override
    public void heal(int portion) {        
        hp += portion;
        if(hp > Hero.MAX_HP)
            hp = Hero.MAX_HP;

        System.out.println("캡틴아메리카가 체력을 " + portion + "만큼 회복했습니다.");
    }

    public void muster() {
        System.out.println("어벤져스 멤버들을 소집합니다.");
    }
}

추상 메소드는 강제적으로 오버라이딩 했고, default 메소드는 선택적으로 오버라이딩 했음을 볼 수 있다.

그리고 부모의 메소드를 오버라이딩할때는 위에서처럼 부모의 메소드보다 넓은 범위의 접근제어자를 지정해야한다.

 

다중 인터페이스 구현 클래스

객체는 다수의 인터페이스 타입으로 사용할 수 있다.

인터페이스는 다중 구현이 가능하다.

여러개의 인터페이스를 구현하는 다중 구현이 가능하다.

구현클래스는 모든 인터페이스의 추상메소드에 대해 실체 메소드를 작성해야한다.

 

컴퓨터의 다양한 모드로 예를 들어보자.

public interface WorkingMode {
    void WorkingMode();
}

public interface GameMode {
    void GameMode();
}

public interface NightMode {
    void NightMode();
}

 

 

아래 Computer 클래스는 위 3개의 인터페이스를 모두 구현한다.

그래서 모든 인터페이스의 추상메서드의 실체 메소드를 오버라이딩하였다.

public class Computer implements WorkingMode, GameMode, NightMode {

    @Override
    public void WorkingMode() {
        System.out.println("문서작업 모드로 변경합니다.");
    }

    @Override
    public void GameMode() {
        System.out.println("게임 모드로 변경합니다.");
    }

    @Override
    public void NightMode () {
        System.out.println("야간 모드로 변경합니다.");
    }
}

 


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

OOP의 개념중 다형성은 자식 클래스의 인스턴스를 부모 타입의 참조변수로 참조하는 것이 가능하다는 것을 말한다.

인터페이스도 해당 인터페이스 타입의 참조변수로 클래스의 인스턴스를 참조할 수 있고, 형변환도 가능하다.

public class Main { 
    public static void main(String[] args) { 
        Hulk hulk1 = new Hulk();
        CaptainAmerica ca1 = new CaptainAmerica();

        hulk1.attack();
        hulk1.heal(30);
        hulk1.sound();
        hulk1.jump();

        ca1.attack();
        ca1.heal(30);
        ca1.muster();

        Hero hulk2 = new Hulk();
        Hero ca2 = new CaptainAmerica();

        hulk2.attack();
        hulk2.heal(30);
        hulk2.sound();
        //hulk2.jump();   X

        ca2.attack();
        ca2.heal(30);
        //ca2.muster();   X

        //형변환
        ((Hulk)hulk2).jump();
        ((CaptainAmerica)ca2).muster();
    }
}

클래스 타입 레퍼런스는 해당 클래스에 정의된 메서드를 호출할 수 있고,

인터페이스 레퍼런스는 해당 인터페이스에 선언된 메서드만 호출할 수 있다.

 


4. 인터페이스 상속

인터페이스는 다른 인터페이스를 상속(확장)할 수 있다.

클래스가 다른 클래스를 상속하는 것과 동일하게 extends 키워드를 사용하여 인터페이스를 상속할 수 있다.

자식 인터페이스에서 부모 인터페이스의 모든 메소드와 상수를 상속받고 새로운 메소드와 상수를 정의할 수 있다.

자식 인터페이스를 구현하게 될 클래스는 자식 인터페이스의 메소드와 그것의 부모인 인터페이스의 메소드까지도 모두 구현해야한다.

 

 

인터페이스의 다중 상속

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

그러나 부모 인터페이스에 있는 메소드 중에서 메소드명과 파라미터 형식은 같지만, 반환 타입은 다른 메소드가 있다면 다중 상속이 불가능하다.

어떤것을 상속받느냐에 따라서 규칙이 달라지기 때문이다.

 

출처 :  https://www.notion.so/4b0cf3f6ff7549adb2951e27519fc0e6

위의 구조에서 인터페이스 A와 B2를 상속받는 인터페이스 C2를 만들려고 할 때, 컴파일 에러가 발생한다.

A에는 void m1() 가 있고, B2에는 int m1()이 있는데 반환타입만 다른 메소드이므로 다중 상속이 불가능하다.

 


5. 인터페이스 기본 메소드(default method), 자바 8

인터페이스는 원래 기능에 대한 선언만 하고, 실제 로직을 구현할 수 없다.

그런데 위에서도 말했듯이 자바8 이후부터는 가능하게 되었다.

그것이 default method 이다.

 

메소드 선언시에 default 키워드를 명시하게 되면 로직을 작성할 수 있다.

여기서 default는 접근제어자의 default와는 약간 다른데, 접근 제어자의 default는 아무것도 명시하지 않은 것을 말하지만 default method는 default 키워드를 명시해주어야 한다.

interface ExampleInterface {
    default void print() {
        System.out.println("hello world!");
    }
}

default method는 구현클래스에서 강제적으로 구현할 필요가 없으며 선택적으로 오버라이딩할 수 있다.

 

 

 

default method는 왜필요할까?

인터페이스의 역할은 보통 기능 구현이 아닌, 틀을 잡아주는 선언에 초점을 두는 것인데도 불구하고 default method가 왜 생겼을까? 결론적으로는 하위 호환성 때문이다.

 

예를 들어서 내가 오픈 소스코드를 만들어서 많은 사람들이 그 코드를 사용하고 있다고 가정하자. 그 소스코드 중 어떤 인터페이스에 새로운 메소드를 만들어야 하는 상황이 발생했다. 내가 거기서 그냥 인터페이스에 추상메소드로 만들어 새로 추가한다면, 그 오픈소스를 사용하던 모든 사람들은 오류가 발생하고 수정해야하는 일이 생길 것이다. 이럴 때 default method로 추가하면, 구현 클래스에 영향을 주지 않으면서 수정할 수 있다.

 

즉, 기존의 인터페이스를 보완하는 과정에서 추가적으로 구현해야할 메소드가 있다면 이미 이 인터페이스를 구현하고 있는 클래스와의 호환성이 떨어지게 된다. 이 때 default method를 추가하게 되면 하위 호환성은 유지하면서 인터페이스를 보완할 수 있다.

 

 

주의사항

만약 여러 인터페이스를 구현하는 클래스가 있다.

그런데 이 인터페이스들에 똑같은 deafult 메소드가 정의되어 있다면?

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

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

public class C implements A, B {
    //이 때는 print() 메소드를 재정의하지 않으면 컴파일 에러가 남.
    @Override
    default void print() {
        System.out.println("I'm C.");
        A.super.print();
        B.super.print();
    }

}

인터페이스 A와 B에 모두 default method인 print() 가 있다.

A와 B를 모두 구현하는 클래스 C는 이 때 print() 메소드를 재정의해야한다.

그렇지 않으면 컴파일 에러가 난다.

그리고 A와 B의 print() 도 사용할 수 있다.

위와 같이 인터페이스명.super.메소드명(); 으로 호출할 수 있다.

 


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

인스턴스 생성과 상관없이 인터페이스 타입으로 호출할 수 있는 메소드이다.

이 static 메소드도 자바8에 도입되어서 인터페이스에 추가 가능하다.

접근제어자는 항상 public으로 간주한다.

그리고 static 메소드이기 때문에 구현 클래스에서 오버라이딩이 불가능하다.

public interface A {
    static void print(){
        System.out.println("hello world!");
    }
}

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

static 메소드는 인터페이스명.메소드명(); 으로 호출할 수 있다.

 


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

자바8에서는 default method, static method가 추가되었고

자바9에서는 private method와 private static method가 추가되었다.

 

private method는 인터페이스에서 로직 구현이 되어야 하고, 추상 메소드가 될수 없다.

구현클래스에서 재정의할수도 없으며, 자식 인터페이스에서도 상속이 불가하다.

 

즉, 외부에 공개되지 않고 인터페이스 내부에서만 사용하도록 구현할 수 있다.

이로써 코드의 중복을 피하고 인터페이스에 대한 캡슐화를 유지할 수 있다.

public interface A {
    default void print(){
        printHello();
        printWorld();
    }

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

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

 

반응형

'Java > 개념정리' 카테고리의 다른 글

[Java] 멀티쓰레드 프로그래밍  (0) 2021.10.14
[Java] 예외 처리  (1) 2021.10.06
[Java] 자바 package  (0) 2021.09.28
[Java] 상속  (2) 2021.09.27
[Java] Queue  (0) 2021.09.23
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday