본문 바로가기
Computer Sience/Java

[JAVA8] 인터페이스 기본 메소드와 스태틱 메소드

by 제우제우 2024. 10. 2.

목차 

  • 기본 메소드 (Default Methods)
  • 스태틱 메소드 (Static Methods)
  • 참고 자료

기본 메서드 (Default Methods)

기본 메서드(Default Methods) ?

  • 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법
  • 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능을 추가할 수 있다
  • 기본 메소드는 구현체가 모르게 추가된 기능으로 그만큼 리스크가 있다
    • 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다
    • 반드시 문서화 할 것. (@implSpec 자바독 태그 사용)
  • Object가 제공하는 기능 (equals, hasCode)는 기본 메소드로 제공할 수 없다
    • 구현체가 재정의해야 한다
  • 본인이 수정할 수 있는 인터페이스에만 기본 메소드를 제공할 수 있다
  • 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다
  • 인터페이스 구현체가 재정의 할 수도 있다.

 

기본 메서드가 필요한(등장) 이유?

// 인터페이스 Foo
public interface Foo {
    void printName();
}

// 인터페이스 Foo 구현한 클래스 DefaultFoo
public class DefaultFoo implements Foo{
    @Override
    public void printName() {
        System.out.println("DefaultFoo");
    }
}

 

이때 이름을 전부 대문자로 출력할 수 있는 메소드를 만들어달라고 요구사항이 발생한다면?

// 인터페이스 Foo
public interface Foo {
    void printName();
    void printNameUpperCase();
}

// 인터페이스 Foo 구현한 클래스 DefaultFoo 
public class DefaultFoo implements Foo{
    @Override
    public void printName() {
        System.out.println("DefaultFoo");
    }
}

 

DefaultFoo 클래스에서는 추상화된 메서드인 printNameUpperCase() 메소드를 구현하지 않았기 때문에

컴파일 에러가 발생한다.

 

지금은 Foo 인터페이스를 상속받은 클래스가 1개지만 만약 엄청 많다면? 

다 하나하나 구현을 하는 게 맞을까?

 

자바에서는 default 메서드를 제공한다.

// 인터페이스 Foo
public interface Foo {
    void printName();
    default void printNameUpperCase(){
    	System.out.println("FOO");
    }
}

// 인터페이스 Foo 구현한 클래스 DefaultFoo 
public class DefaultFoo implements Foo{
    @Override
    public void printName() {
        System.out.println("DefaultFoo");
    }
}

 

default 메서드 활용하기 

public interface Foo {
    void printName();
    default void printNameUpperCase(){
       	System.out.println(getName().toUpperCase());
    }
    String getName();
}

public class DefaultFoo implements Foo{
    private String name;
    public DefaultFoo(String name) {
        this.name = name;
    }
    @Override
    public void printName() {
        System.out.println(this.name);
    }
    @Override
    public String getName(){
        return this.name;
    }
}

public class Test {
    public static void main(String[] args) {
        Foo foo = new DefaultFoo("jeu");
        foo.printName();
        foo.printNameUpperCase();
    }
}

 

기본 메소드는 구현체가 모르게 추가된 기능으로 그만큼 리스크가 있다

 

주의: 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다

public class Test {
    public static void main(String[] args) {
        Foo foo = new DefaultFoo(null);
        foo.printName();
        foo.printNameUpperCase();  // 런타임 에러 발생 null.toUpperCase();
    }
}

현재 DefaultFoo 객체를 생성하면서 생성자로 인스턴스 변수 이름에 null 값을 넘기고 있다.

이름 출력은 그냥 null 값을 출력하지만 

printNameUpperCase() 에서는 null.toUpperCase() 동작을 하여 런타임 에러가 발생한다. 

 

반드시 문서화 하자. (@implSpec 자바독 태그 사용)

public interface Foo {
    void printName();
    /**
     * getName()의 리턴값인 문자열을 대문자로 바꿔서 반환한다.
     *
     * @return String
     * @implSpec
     * 이 메서드는 toUpperCase()를 사용하기 때문에 getName() 리턴값이 null이면 NullPointException이 발생합니다.
     */
    default void printNameUpperCase(){
       	System.out.println(getName().toUpperCase());
    }
    String getName();
}

 

인터페이스 구현체가 재정의 할 수도 있다.

public interface Foo {
    void printName();
    default void printNameUpperCase(){
       	System.out.println(getName().toUpperCase());
    }
    String getName();
}
public class DefaultFoo implements Foo{
    private String name;
    public DefaultFoo(String name) {
        this.name = name;
    }
    @Override
    public void printName() {
        System.out.println(this.name);
    }
    @Override
    public String getName(){
        return this.name;
    }
    // 재정의 
    @Override 
    public void printNameUpperCase(){
        System.out.println(getName().toUpperCase());
    }
}

 

 

인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다

public interface Foo {
    void printName();
    default void printNameUpperCase(){
       	System.out.println(getName().toUpperCase());
    }
    String getName();
}
public interface Bar extends Foo{
    void printNameUpperCase();
}

 

물론 Bar 인터페이스를 구현하는 구현체는 추상 메소드로 바뀐 printNameUpperCase()를 다시 구현해야 한다. 

 

다이아몬드 Problem

 

다이아몬드 문제란?

다이아몬드 문제는 다중 상속 시, 동일한 메서드를 여러 조상(여기서는 인터페이스)으로부터 상속받을 때 발생한다.

자바는 이 문제를 명시적으로 해결하도록 요구하며, 두 인터페이스 중 어떤 메서드를 사용할지 지정하거나, 충돌하는 메서드를 직접 오버라이딩하는 방식으로 해결할 수 있다.

public interface Bar{
    default void printNameUpperCase(){
        System.out.println("Bar");
    }
}
public interface Foo {
    void printName();
    default void printNameUpperCase(){
       	System.out.println(getName().toUpperCase());
    }
    String getName();
}
// 컴파일 에러 발생 
public class DefaultFoo implements Foo, Bar{
    private String name;
    public DefaultFoo(String name) {
        this.name = name;
    }
    @Override
    public void printName() {
        System.out.println(this.name);
    }
    @Override
    public String getName(){
        return this.name;
    }
}

 

현재 DefaultFoo 클래스를 보면 Foo 인터페이스와 Bar 인터페이스 모두 구현하고 있다. 

하지만 메소드 중복이 일어난다. (다이아몬드 문제)

이때 자바는 컴파일 에러를 발생시킨다. 


스태틱 메서드 (Static Methods)

해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있다.

 

활용 

public interface Foo {
    void printName();
    // 스태틱 메소드 
    static void printAny(){
        System.out.println("static");
    }
}

public class Test {
    public static void main(String[] args) {
        Foo.printAny(); // static 출력 
    }
}

 


참고 자료 

인프런 백기선님 더 자바, Java 8

 

더 자바, Java 8 강의 | 백기선 - 인프런

백기선 | 자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합

www.inflearn.com

Java8 공식 문서 Default Methods

 

Default Methods (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com