Java 인터페이스 진화: 모범 사례 및 전략

발행: (2025년 12월 27일 오후 08:29 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

Java 인터페이스의 진화 – Java 8 이후 LTS 릴리스

릴리스날짜
Java 82014년 3월
Java 112018년 9월
Java 172021년 9월
Java 212023년 9월

📍 2014년 3월 – Java 8

함수형 혁명

Java 8은 비‑추상 메서드를 허용함으로써 인터페이스를 변형했습니다. 이는 인터페이스 진화 문제를 해결하고 함수형 인터페이스를 통한 함수형 프로그래밍의 문을 열었습니다.

새로운 인터페이스 기능

  • Default 메서드 – 인터페이스 내부에 구체적인 구현을 제공 (default 키워드).
  • Static 메서드 – 인터페이스 자체에 정의된 유틸리티 메서드.
  • Functional 인터페이스 – 추상 메서드가 정확히 하나인 인터페이스로, 람다와 함께 사용할 수 있음.

1. Default Methods

public interface MyInterface {
    void existingMethod();

    default void newDefaultMethod() {
        System.out.println("This is a default method.");
    }
}
public class MyClass implements MyInterface {
    @Override
    public void existingMethod() {
        System.out.println("Implementing existing method.");
    }

    // 커스텀 동작이 필요하지 않은 한 newDefaultMethod()를 구현할 필요 없음
    public void someMethod() {
        newDefaultMethod();   // 기본 구현을 호출
    }
}

실제 예시

java.util.Listsort

public interface List extends Collection {
    default void sort(Comparator c) {
        Collections.sort(this, c);
    }
}

// 사용 예
List list = new ArrayList<>(Arrays.asList("banana", "apple", "cherry"));
list.sort(Comparator.naturalOrder());
System.out.println(list);   // → [apple, banana, cherry]

java.lang.IterableforEach

public interface Iterable {
    default void forEach(Consumer action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

// 사용 예
List list = Arrays.asList("apple", "banana", "cherry");
list.forEach(item -> System.out.println(item));

java.util.Map – 여러 기본 메서드

public interface Map {
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;
    }

    default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }
        return v;
    }

    // … 기타 기본 메서드: remove, replace, compute, merge, …
}

// 사용 예
Map map = new HashMap<>();
map.put("apple", 1);
Integer i = map.getOrDefault("banana", 0);
System.out.println(i);   // → 0

2. 인터페이스의 정적 메서드

public interface MyInterface {
    static void utilityMethod() {
        System.out.println("This is a static method in the interface.");
    }
}
public class MyClass implements MyInterface {
    public void someMethod() {
        MyInterface.utilityMethod();   // 정적 메서드를 호출합니다
    }
}

유틸리티 클래스 예시 – java.util.Collections

public final class Collections {
    public static List emptyList() {
        return (List) EMPTY_LIST;
    }
}

// 사용 예시
List empty = Collections.emptyList();
System.out.println(empty);   // → []

Map의 팩토리 메서드

public interface Map {
    static Map of() {
        return new HashMap<>();
    }

    static Map of(K k1, V v1) {
        Map map = new HashMap<>();
        map.put(k1, v1);
        return map;
    }

    // … overloads for more entries …
}

// 사용 예시
Map map = Map.of("apple", 1, "banana", 2);
System.out.println(map);   // → {apple=1, banana=2}

3. 함수형 인터페이스

@FunctionalInterface
public interface MyFunctionalInterface {
    void singleAbstractMethod();
}

// Lambda 사용
MyFunctionalInterface func = () -> System.out.println("Lambda expression implementation.");
func.singleAbstractMethod();   // → Lambda expression implementation.

java.util.function 패키지

인터페이스목적예시
Predicate하나의 인자를 받아 boolean 값을 반환하는 함수Predicate<String> isEmpty = s -> s.isEmpty();
Function하나의 인자를 받아 결과를 반환하는 함수Function<String,Integer> length = s -> s.length();
Consumer단일 입력 인자에 대해 작업을 수행하는 함수Consumer<String> printer = System.out::println;

Predicate

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

// 사용법
Predicate<String> isEmpty = str -> str.isEmpty();
System.out.println(isEmpty.test(""));      // → true
System.out.println(isEmpty.test("hello")); // → false

Function

@FunctionalInterface
public interface Function<T,R> {
    R apply(T t);
}

// 사용법
Function<String,Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("hello")); // → 5
System.out.println(stringLength.apply(""));      // → 0

Consumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

// 사용법
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello, world!");   // → Hello, world!

Source:

Java의 함수형 인터페이스

java.util.function.Consumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

// 사용법
Consumer<String> print = str -> System.out.println(str);
print.accept("hello");   // → hello
print.accept("world");   // → world

java.util.function.Supplier

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

// 사용법
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get()); // → Random double value
System.out.println(randomValue.get()); // → Another random double value

java.util.function.UnaryOperator (extends Function)

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T,T> {}

// 사용법
UnaryOperator<Integer> square = x -> x * x;
System.out.println(square.apply(5)); // → 25
System.out.println(square.apply(0)); // → 0

java.util.function.BinaryOperator (extends BiFunction)

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}

// 사용법
BinaryOperator<Integer> add = (x, y) -> x + y;
BinaryOperator<Integer> addWithMethodReference = Integer::sum;
System.out.println(add.apply(5, 10));                 // → 15
System.out.println(addWithMethodReference.apply(0,0)); // → 0

Java 8 이전 함수형 스타일 인터페이스

InterfacePackageAbstract MethodCommon Use
Runnablejava.langvoid run()스레딩, executor
Callablejava.util.concurrentV call()반환값이 있는 스레딩
Comparatorjava.utilint compare(T a, T b)정렬
ActionListenerjava.awt.eventvoid actionPerformed(ActionEvent e)GUI 이벤트

이 인터페이스들은 Java 8 이전에 정의되었으며 이후 함수형 인터페이스로 지정되었습니다.

인터페이스 기능의 진화

2018 년 9월 – Java 11

기능: 인터페이스의 private 메서드 (Java 9에 도입되고 Java 11에서도 계속 지원).

public interface DataProcessor {

    default void process(String data) {
        String cleaned = sanitize(data);
        System.out.println("Processed: " + cleaned);
    }

    private String sanitize(String input) {
        return input.trim().toLowerCase();
    }

    static DataProcessor create() {
        return new DataProcessor() {};
    }
}

// Usage
public class TestInterfacePrivateMethod {
    public static void main(String[] args) {
        DataProcessor processor = DataProcessor.create();
        processor.process("  HELLO WORLD  "); // → Processed: hello world
    }
}

Java 11은 LTS 릴리스이지만, private‑method 지원 외에 새로운 인터페이스 기능은 추가되지 않았습니다.

2021 년 9월 – Java 17

기능: Sealed 인터페이스 (sealed 클래스와 함께 제공).

// Sealed interface – only the listed types may implement it.
public sealed interface Shape permits Circle, Rectangle, Polygon {}

// Final class – cannot be subclassed.
final class Circle implements Shape {
    public final double radius;
    public Circle(double radius) { this.radius = radius; }
}

// Sealed subclass – continues the restriction.
sealed class Polygon implements Shape permits Quadrilateral {
    public final int sides;
    public Polygon(int sides) { this.sides = sides; }
}

// Non‑sealed class – can be freely extended.
non-sealed class Rectangle implements Shape {
    public final double width, height;
    public Rectangle(double w, double h) { width = w; height = h; }
}

규칙

  • 서브타입은 permits 절에 명시되어야 하며(또는 같은 소스 파일에 있어야 함).
  • 허용된 각 서브타입은 final, sealed, 혹은 non‑sealed 로 선언되어야 합니다.
  • non‑sealed는 sealed 타입을 직접 상속하는 클래스(또는 인터페이스)에서만 사용할 수 있습니다.

2023 년 9월 – Java 21

기능: switch에 대한 패턴 매칭 (인터페이스 전용 새로운 기능은 없음).

sealed interface Expr permits Constant, Add {}

record Constant(int value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}

public static int eval(Expr e) {
    return switch (e) {
        case Constant c -> c.value;
        case Add a      -> eval(a.left()) + eval(a.right());
    };
}

// Usage
Expr expr = new Add(new Constant(3), new Constant(5));
System.out.println(eval(expr)); // → 8

인터페이스 설계 모범 사례

  • 기본 메서드는 최소한으로 사용 – 기본 메서드는 선택적 기능을 추가해야 하며, 핵심 계약을 변경해서는 안 됩니다.
  • 기존 구현에 미치는 영향을 고려 – 기본 메서드를 추가하면 현재 모든 구현자에게 영향을 줄 수 있습니다.
  • 유틸리티에는 정적 메서드를 선호 – 인터페이스의 목적과 밀접하게 관련되도록 유지합니다.
  • 기본 및 정적 메서드를 명확히 문서화 – 의도, 사용법 및 부작용을 설명합니다.
  • 인터페이스에 상태를 두지 않음 – 기본 메서드는 구현 클래스의 상태를 활용해야 하며, 자체 상태를 유지해서는 안 됩니다.

마무리 생각

정적 애플리케이션 보안 테스트(SAST)는 성숙한 보안 소프트웨어 개발 라이프사이클(SSDLC)의 핵심 요소로 남아 있습니다. 이는 초기의 실행 가능한 인사이트를 제공하고, 위험을 감소시키며, 개발자를 강화하고, 배포 전 애플리케이션을 견고하게 만듭니다. SAST가 단순한 탐지 도구에서 장기적인 보안 엔지니어링 문화의 촉매제로 발전할 때, 전체 개발 생태계가 혜택을 받습니다.

Back to Blog

관련 글

더 보기 »