JAVA/JAVA8

Default/Static Methods in Interfaces

최-코드 2024. 8. 27. 11:35

자바 8 이전 인터페이스에는 메서드를 선언만 할 수 있었고, 메서드 구현은 할 수 없었다. 이러한 제한 사항은 인터페이스에 기능을 추가하는데에 큰 어려움을 주었다. 예를 들어, 새로운 메소드를 인터페이스에 추가할 때, 이미 이 인터페이스를 구현한 모든 클래스들은 컴파일 에러를 피하기 위해 새롭게 추가된 메서드도 구현해야 했다.

이로 인해 새로운 기능을 인터페이스에 추가하는 것은 쉽지 않았지만, java 8부터 인터페이스에 static 메소드와 default 메서드를 통해 메소드 구현을 허용하게 되었다.

 

Default 메소드

  • 인터페이스 내에서 메소드 구현을 허용하면서도, 이미 implements한 클래스들에게 영향을 주지 않기 위해 도입되었다.
  • 기본적인 포맷은 default void function() { System.out.println("default 메소드") }이다. 리턴 타입 앞에 default 키워드를 추가하면 된다. 이 때 접근 제어자를 붙이면 컴파일 오류가 발생한다. default 키워드가 붙은 메소드는 기본적으로 public으로 설정된다.

Static 메소드

  • defulat 메소드와 마찬가지로 인터페이스 내에 구현된 메소드를 만들 수 있게 해주는 키워드이다.
  • default 메소드와의 주요 차이점은 static 메소드는 구현 클래스에서 오버라이딩할 수 없다는 것이다.
  • 인터페이스와만 연관된 유틸리티 메소드를 제공할 때 유용하다.
  • 클래스에서 static 메소드를 정의한 것과 마찬가지로, 변수를 통해 접근하는 것이 아닌, 인터페이스명을  통해 접근해야 한다.
public static void main(String[] args) {
    Multiplier multiplier = new MultiplierImpl();

    List<Integer> list = IntStream.rangeClosed(1, 5).boxed().toList();

    System.out.println(multiplier.size(list));
    System.out.println(multiplier.multiply(list));
    System.out.println(Multiplier.isEmpty(list));
}

 

추상 클래스 vs 인터페이스 

  • 인스턴스 변수
    • 추상 클래스 - 추상 클래스는 인스턴스 변수를 가질 수 있다.
    • 인터페이스 - 인터페이스는 인스턴스 변수를 가질 수 없고, 오직 상수(static final) 변수만 가질 수 있다.
  • 상속
    • 추상 클래스 - 한 클래스는 오직 하나의 추상 클래스만 상속할 수 있다.
    • 인터페이스 - 한 클래스는 여러 개의 인터페이스를 구현할 수 있다.

이러한 점과 default 메소드의 등장을 미뤄보았을 때 java 8 이후 다중 상속이 가능해졌다는 걸 알 수 있다.

 

다중 상속

public interface Interface1 {
    default void methodA(){
        System.out.println("1A");
    }
}

public interface Interface2 extends Interface1{
    @Override
    default void methodA() {
        System.out.println("2A");
    }

    default void methodB(){
        System.out.println("2B");
    }
}

public interface Interface3 extends Interface2{
    default void methodC(){
        System.out.println("3C");
    }
    
    @Override
    default void methodB() {
        System.out.println("3B");
    }
}

public class Client123 implements Interface1, Interface2, Interface3{
    public static void main(String[] args) {
        Client123 client123 = new Client123();
        client123.methodA();
        client123.methodB();
        client123.methodC();
    }

    @Override
    public void methodA() {
        System.out.println("ClientA");
    }
}
//출력 :
//ClientA
//3B
//3C

오버라이딩의 우선순위는 구현 클래스 -> 자식 인터페이스 -> 부모 인터페이스이다. 

 

다중 상속 충돌

  • java 8 이전에 다중 상속을 지원하지 않은 이유는 다이아몬드 문제 때문이다. FatherA와 FatherB가 오버라이딩한 GrandFather의 메소드를 Son이 호출하려고 할 때 에러가 발생하기 때문에 애초에 클래스의 다중 상속을 막았다. 즉, extends에 여러 개의 클래스를 쓰면 애초에 이 부분에서 컴파일 에러가 발생한다.
  • 하지만 위에서 말했듯 implement는 여러 개 할 수 있다. 하지만 같은 이름의 default 메소드가 존재하면 비로소 컴파일 에러가 발생한다. 이를 해결하려면 interface를 implement한 클래스에서 중복된 메소드를 오버라이딩 해주면 된다.
public interface Interface1 {
    default void methodA(){
        System.out.println("1A");
    }
}

public interface Interface4 {
    default void methodA(){
        System.out.println("4A");
    }
}

public class Client14 implements Interface1, Interface4{
    @Override
    public void methodA() {
        System.out.println("ClientA");
    }

    public static void main(String[] args) {
        Client14 client14 = new Client14();
        client14.methodA();
    }
}
//출력 : ClientA