1. 개요

Java 8은 람다 표현식 , 기능 인터페이스 , 메소드 참조 , 스트림 , Optional 및 인터페이스의 정적기본 메소드를 포함하여 몇 가지 새로운 기능을 테이블에 가져왔습니다 .

우리는 이미 다른 기사 에서 이러한 기능 중 일부를 다루었습니다 . 그럼에도 불구하고 인터페이스의 정적기본 메서드는 그 자체로 더 깊이 살펴볼 가치가 있습니다.

이 사용방법(예제)에서는 인터페이스에서 정적기본 메서드 를 사용하는 방법을 배우고 유용할 수 있는 몇 가지 상황에 대해 논의합니다.

2. 인터페이스에 기본 메소드가 필요한 이유

일반 인터페이스 메서드와 마찬가지로 기본 메서드는 암시적으로 public입니다. public 한정자 를 지정할 필요가 없습니다 .

일반 인터페이스 메소드와 달리 메소드 서명의 시작 부분에 default 키워드를 사용하여 선언하고 구현 을 제공합니다 .

간단한 예를 살펴보겠습니다.

public interface MyInterface {
    
    // regular interface methods
    
    default void defaultMethod() {
        // default method implementation
    }
}

Java 8 릴리스에 기본 메소드가 포함된 이유 는 매우 분명합니다.

인터페이스에 하나 이상의 구현이 있는 추상화를 기반으로 하는 일반적인 디자인에서 인터페이스에 하나 이상의 메서드가 추가되면 모든 구현도 강제로 구현해야 합니다. 그렇지 않으면 디자인이 무너질 것입니다.

기본 인터페이스 메서드는 이 문제를 처리하는 효율적인 방법입니다. 이를 통해 구현에서 자동으로 사용할 수 있는 인터페이스에 새 메서드를 추가할 수 있습니다 . 따라서 구현 클래스를 수정할 필요가 없습니다.

이런 식으로 구현자를 리팩토링할 필요 없이 이전 버전과의 호환성이 깔끔하게 유지 됩니다.

3. 기본 인터페이스 메소드 실행

기본 인터페이스 메서드 의 기능을 더 잘 이해하기 위해 간단한 예제를 만들어 보겠습니다.

순진한 Vehicle 인터페이스와 단 하나 의 구현이 있다고 가정 합니다. 더 있을 수 있지만 간단하게 유지하겠습니다.

public interface Vehicle {
    
    String getBrand();
    
    String speedUp();
    
    String slowDown();
    
    default String turnAlarmOn() {
        return "Turning the vehicle alarm on.";
    }
    
    default String turnAlarmOff() {
        return "Turning the vehicle alarm off.";
    }
}

이제 구현 클래스를 작성해 보겠습니다.

public class Car implements Vehicle {

    private String brand;
    
    // constructors/getters
    
    @Override
    public String getBrand() {
        return brand;
    }
    
    @Override
    public String speedUp() {
        return "The car is speeding up.";
    }
    
    @Override
    public String slowDown() {
        return "The car is slowing down.";
    }
}

마지막으로 Car 인스턴스를 만들고 해당 메서드를 호출 하는 일반적인 기본 클래스를 정의해 보겠습니다.

public static void main(String[] args) { 
    Vehicle car = new Car("BMW");
    System.out.println(car.getBrand());
    System.out.println(car.speedUp());
    System.out.println(car.slowDown());
    System.out.println(car.turnAlarmOn());
    System.out.println(car.turnAlarmOff());
}

Vehicle 인터페이스 기본 메소드인 turnAlarmOn()turnAlarmOff() 가 Car 클래스 에서 어떻게 자동으로 사용 가능한지 확인하십시오 .

게다가, 어떤 시점에서 우리가 Vehicle 인터페이스에 더 많은 기본 메소드를 추가하기로 결정한다면 , 애플리케이션은 계속 작동할 것이고 우리는 클래스가 새로운 메소드에 대한 구현을 제공하도록 강요할 필요가 없을 것입니다.

인터페이스 기본 메소드의 가장 일반적인 용도는 구현 클래스를 분해하지 않고 주어진 유형에 추가 기능을 점진적으로 제공하는 것입니다.

또한 기존 추상 메서드에 대한 추가 기능 을 제공하는 데 사용할 수 있습니다 .

public interface Vehicle {
    
    // additional interface methods 
    
    double getSpeed();
    
    default double getSpeedInKMH(double speed) {
       // conversion      
    }
}

4. 다중 인터페이스 상속 규칙

기본 인터페이스 방법은 꽤 좋은 기능이지만 언급할 가치가 있는 몇 가지 주의 사항이 있습니다. Java는 클래스가 여러 인터페이스를 구현할 수 있도록 허용하므로 클래스 가 동일한 기본 메소드 를 정의하는 여러 인터페이스를 구현할 때 어떤 일이 발생하는지 아는 것이 중요 합니다 .

이 시나리오를 더 잘 이해하기 위해 새 Alarm 인터페이스를 정의하고 Car 클래스 를 리팩터링 하겠습니다.

public interface Alarm {

    default String turnAlarmOn() {
        return "Turning the alarm on.";
    }
    
    default String turnAlarmOff() {
        return "Turning the alarm off.";
    }
}

고유한 기본 메서드 집합을 정의하는 이 새 인터페이스를 사용하여 Car 클래스는 VehicleAlarm 을 모두 구현합니다 .

public class Car implements Vehicle, Alarm {
    // ...
}

이 경우 코드는 단순히 컴파일되지 않습니다. 다중 인터페이스 상속 ( Diamond Problem 이라고도 함)으로 인해 충돌이 발생하기 때문 입니다. Car 클래스는 두 기본 메서드 집합을 모두 상속합니다 . 그래서 우리는 누구를 호출해야합니까?

이 모호성을 해결하려면 메서드에 대한 구현을 명시적으로 제공해야 합니다.

@Override
public String turnAlarmOn() {
    // custom implementation
}
    
@Override
public String turnAlarmOff() {
    // custom implementation
}

또한 클래스 가 인터페이스 중 하나의 기본 메서드를 사용하도록 할 수도 있습니다 .

Vehicle 인터페이스 의 기본 메서드 를 사용하는 예를 살펴보겠습니다 .

@Override
public String turnAlarmOn() {
    return Vehicle.super.turnAlarmOn();
}

@Override
public String turnAlarmOff() {
    return Vehicle.super.turnAlarmOff();
}

마찬가지로 클래스 가 Alarm 인터페이스 에 정의된 기본 메서드를 사용하도록 할 수 있습니다.

@Override
public String turnAlarmOn() {
    return Alarm.super.turnAlarmOn();
}

@Override
public String turnAlarmOff() {
    return Alarm.super.turnAlarmOff();
}

Car 클래스가 두 가지 기본 메소드 세트를 모두 사용 하도록 하는 것도 가능합니다 .

@Override
public String turnAlarmOn() {
    return Vehicle.super.turnAlarmOn() + " " + Alarm.super.turnAlarmOn();
}
    
@Override
public String turnAlarmOff() {
    return Vehicle.super.turnAlarmOff() + " " + Alarm.super.turnAlarmOff();
}

5. 정적 인터페이스 방법

인터페이스에서 기본 메소드 를 선언하는 것 외에도 Java 8을 사용하면 인터페이스에서 정적 메소드 를 정의하고 구현할 수 있습니다 .

정적 메서드는 특정 개체에 속하지 않으므로 인터페이스를 구현하는 클래스의 API에 속하지 않습니다 . 따라서 메서드 이름 앞에 인터페이스 이름을 사용하여 호출 해야 합니다 .

인터페이스에서 정적 메서드가 작동 하는 방식을 이해하기 위해 Vehicle 인터페이스를 리팩토링하고 정적 유틸리티 메서드를 추가해 보겠습니다.

public interface Vehicle {
    
    // regular / default interface methods
    
    static int getHorsePower(int rpm, int torque) {
        return (rpm * torque) / 5252;
    }
}

인터페이스 내에서 정적 메서드를 정의 하는 것은 클래스에서 정의하는 것과 동일합니다. 또한 정적 메서드는 다른 정적기본 메서드 내에서 호출할 수 있습니다 .

주어진 차량 엔진의 마력 을 계산하려고 한다고 가정해 봅시다 . getHorsePower() 메서드 를 호출하기만 하면 됩니다.

Vehicle.getHorsePower(2500, 480));

정적 인터페이스 메서드 의 이면에 있는 아이디어 는 개체를 만들지 않고도 관련 메서드를 한 곳에 모아서 디자인 의 응집도 를 높일 수 있는 간단한 메커니즘을 제공하는 것 입니다.

추상 클래스에서도 마찬가지입니다. 주요 차이점은 추상 클래스가 생성자, 상태 및 동작을 가질 수 있다는 것 입니다.

또한 인터페이스의 정적 메서드를 사용하면 단순히 정적 메서드의 자리 표시자인 인공 유틸리티 클래스를 만들지 않고도 관련 유틸리티 메서드를 그룹화할 수 있습니다.

6. 결론

이 기사에서 우리는 Java 8에서 정적기본 인터페이스 메소드 의 사용에 대해 깊이 탐구했습니다 . 언뜻 보기에 이 기능은 특히 객체 지향 순수주의적 관점에서 약간 조잡해 보일 수 있습니다. 이상적으로 인터페이스는 동작을 캡슐화해서는 안 되며 특정 유형의 공개 API를 정의하는 데만 인터페이스를 사용해야 합니다.

그러나 기존 코드와의 하위 호환성을 유지하는 데 있어 정적기본 메서드는 좋은 절충안입니다.

평소와 같이 이 문서에 표시된 모든 코드 샘플은 GitHub에서 사용할 수 있습니다 .

Generic footer banner