본문 바로가기
Backend/Java

Java 기초 문법 : 인터페이스(interface)

by 코딩쥐 2024. 6. 19.

인터페이스는 추상메서드의 집합으로 오직 추상메서드(public abstract)와 상수(public static)만을 멤버로 가질 수 있다. 모든 메서드는 public abstract 이며 (단 static, default 메서드 제외), 모든 멤버 변수는 public static final 이어야 하며 상수이기 때문에 선언 시 반드시 초기화를 해야한다. 제어자의 경우에는 생략을 해도 컴파일러가 자동적으로 추가한다. 

  • interface 인터페이스이름 {
        public static final 타입 상수이름 = 값;          //상수
        public abstract void 메서드이름(매개변수 목록); //추상메서드
    }
package com.example1;

public interface Food {
    int COST = 10000; // public static final 생략가능

    public abstract void cook();
    void eat(); // public abstract 생략가능
}

 

인터페이스의 장점

두 대상(객체) 간의 '연결, 대화, 소통'을 돕는 '중간 역할'을하며, 선언(설계)와 구현을 분리시킬 수 있게 하여 변경하는 것에 유연하게 대처할 수 있다. 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시 개발 가능해 개발 시간을 단축시킨다.


인터페이스 상속

인터페이스 조상은 인터페이스만 가능하며(Object가 최고 조상이 아니다), 다중 상속이 가능하다. 상속 받은 클래스는 추상 메서드를 다 구현해야한다.

 

클래스에서 인터페이스 상속

  • class 클래스이름 implements 인터페이스이름{
    //인터페이스에 정의된 추상메서드 구현
    }
package com.example1;

// 인터페이스 상속
public class Apple implements Food{

    //추상메서드 구현
    @Override
    public void cook() {
        System.out.println("사과쨈을 만든다.");
    }

    @Override
    public void eat() {
        System.out.println("사과를 잘라서 먹는다.");
    }
}

 

만약 인터페이스 메서드 중 일부만 구현하고 싶을 경우 abstract을 붙여서 추상클래스로 선언해야 한다.

package com.example1;

// 만약 메서드 일부만 구현하고 싶을 경우에는 abstract을 붙여 추상클래스로 선언!
public abstract class Apple implements Food{
    @Override
    public void cook() {
        System.out.println("사과쨈을 만든다.");
    }

    public abstract void eat();
}

 

인터페이스에서 인터페이스 상속

  • interface 인터페이스이름 extends 인터페이스이름{
    //인터페이스에 정의된 추상메서드 구현
    }
package com.example1;

public interface FastFood extends Food{
    //패스트푸드의 칼로리 값을 외부에서 제공받는 메서드
    void getCalories(int calorie);

    // Food 인터페이스의 cook오버라이드
    @Override
    void cook();
}

 

인터페이스를 이용한 다형성

  • 인터페이스를 구현한 클래스(Apple)의 인스턴스를 인터페이스 타입(Food)의 변수로 참조가 가능하다.
        Food myFood = new Apple();

 

  • 인터페이스 타입으로 형변환이 가능하다.
        Apple myApple = (Apple)myFood;
        myApple.eat(); // 사과를 잘라서 먹는다.

 

  • 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능하다.
package com.example1;

public class FoodExample {
    // 인터페이스 타입 매개변수로
    public static void preparedFood(Food food) {
        food.cook(); // Food 인터페이스의 cook 메서드 호출
    }

    public static void main(String[] args) {
        // 인터페이스를 구현한 클래스의 객체(Apple)만 가능하다. 
        Food apple = new Apple(); // Apple 객체 생성
        preparedFood(apple); // 사과쨈을 만든다.
    }
}

 

  • 인터페이스를 메서드의 리턴타입(Food getFood())으로 지정할 수 있다. 인터페이스를 구현한 클래스의 인스턴스(new Apple())를 반환한다.
public Food getFood() {
    return new Apple(); // Apple 객체를 반환
}

 

  • 두 클래스로부터 상속을 받아야 할 상황에서 어느 한쪽의 필요한 부분을 뽑아 인터페이스로 만들어서 다중상속의 문제(충돌)를 해결할 수 있다.

    아래 예시의 경우 Food 인터페이스가 기본 기능을 제공하고, FastFood 인터페이스가 기능을 확장하여 특정 기능을 추가한다. Burger는 최종적으로  FastFood를 상속받아 해당 메서드들을 구현하기 때문에, 다중 상속 문제를 피할 수 있다. 
public interface Food {
    void cook();
    void eat();
}
public interface FastFood extends Food {
    void getCalories(int calorie);
}
public class Burger implements FastFood {
    @Override
    public void cook() {
        System.out.println("버거를 굽는다.");
    }

    @Override
    public void eat() {
        System.out.println("버거를 먹는다.");
    }

    @Override
    public void getCalories(int calorie) {
        System.out.println("버거의 칼로리는 " + calorie + "입니다.");
    }
}

default메서드와 static메서드

JDK1.8부터 인터페이스에서 default 메서드와 static 메서드를 작성할 수 있게 되었다. 기존에 있던 인터페이스에 새로운 메서드를 추가해야 할 때, 이를 추상 메서드로 정의하면 모든 클래스에서 해당 기능을 구현해야 했다. 이는 기존 클래스의 기능을 변경하거나 추가하는데 어려움을 발생시켰다. 이러한 문제를 해결하기 위하여 default 메서드가 도입되었고, default 메서드를 통해 인터페이스에 새로운 기능을 추가하더라도 기존 클래스를 변경하지 않고도 기본 구현을 제공할 수 있게 되었다. 

메서드 설명
디폴트메서드 추상 메서드의 기본적인 구현을 제공하는 메서드
디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경 안해도 됨 
몸통({})이 있어야 함 (ex. default void newMethod(){}) 
접근제어자 public
 static 메서드 접근제어자 public

 

디폴트 메서드가 기존의 메서드와 충돌할 때의 해결책 

1. 여러 인터페이스의 디폴트 메서드 간의 충돌

    : 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야함

2. 디폴트 메서드와 조상클래스의 메서드 간의 충돌

    : 조상 클래스의 메서드가 상속되고 디폴트 메서드는 무시됨