Java 인터페이스 진화: 모범 사례 및 전략
Source: Dev.to
Java 인터페이스의 진화 – Java 8 이후 LTS 릴리스
| 릴리스 | 날짜 |
|---|---|
| Java 8 | 2014년 3월 |
| Java 11 | 2018년 9월 |
| Java 17 | 2021년 9월 |
| Java 21 | 2023년 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.List – sort
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.Iterable – forEach
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 이전 함수형 스타일 인터페이스
| Interface | Package | Abstract Method | Common Use |
|---|---|---|---|
Runnable | java.lang | void run() | 스레딩, executor |
Callable | java.util.concurrent | V call() | 반환값이 있는 스레딩 |
Comparator | java.util | int compare(T a, T b) | 정렬 |
ActionListener | java.awt.event | void 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가 단순한 탐지 도구에서 장기적인 보안 엔지니어링 문화의 촉매제로 발전할 때, 전체 개발 생태계가 혜택을 받습니다.