[Java] predicate, consumer, supplier, function 이해하기 (함수형 인터페이스)
들어가며
Java에서 Predicate, Consumer, Supplier, Function은 java.util.function 패키지에 포함된 대표적인 함수형 인터페이스이다. Java 8부터 도입된 람다식과 함께 사용되며, 각 인터페이스는 고정된 목적에 맞게 설계되어 있어 함수형 프로그래밍 스타일을 가능하게 한다.
함수형 인터페이스란?
함수형 인터페이스는 단 하나의 추상 메서드만 가지는 인터페이스로, 람다 표현식을 통해 간단히 구현할 수 있다.
@FunctionalInterface
public interface MyFunction {
void execute();
}
@FunctionalInterface는 필수는 아니지만, 해당 인터페이스가 함수형임을 명시하고 추상 메서드가 2개 이상이면 컴파일 에러를 발생시켜 준다.
자바에서 제공하는 주요 함수형 인터페이스 (java.util.function 패키지)
1. Predicate
- 추상 메소드 : boolean test(T t)
- 리턴 타입 : boolean
입력값을 받아서 True/False를 판단할 때 사용한다.
Predicate<String> isNotEmpty = str -> !str.isEmpty();
System.out.println(isNotEmpty.test("hello")); // true
System.out.println(isNotEmpty.test("")); // false
활용 예제
List<String> list = List.of("apple", "", "banana", " ");
list.stream()
.filter(s -> !s.trim().isEmpty())
.forEach(System.out::println);
2. Consumer
- 추상 메소드 : void accept(T t)
주로 로그 출력, 화면 출력, DB 저장 등 side effect용 작업에 적합하다.
Consumer<String> printer = s -> System.out.println("출력: " + s);
printer.accept("Hello"); // 출력: Hello
활용 예제
List<String> names = List.of("Kim", "Lee", "Park");
names.forEach(name -> System.out.println("이름: " + name));
3. Supplier
입력은 없고 출력(리턴)만 있는 인터페이스이다.
- 추상 메소드 : T get()
무언가를 생성해서 반환하는 역할을 한다.
Supplier<Integer> randomSupplier = () -> new Random().nextInt(100);
System.out.println("랜덤 숫자: " + randomSupplier.get());
활용 예제
Supplier<String> defaultName = () -> "Anonymous";
String name = null;
System.out.println(name != null ? name : defaultName.get());
4. Function<T, R>
입력값 T를 받아서 R을 리턴하는 인터페이스이다.
- 추상 메소드 : R apply(T t)
데이터 변환, 매핑, 계산 로직에 활용된다.
Function<String, Integer> stringToLength = s -> s.length();
System.out.println(stringToLength.apply("hello"));
활용 예제
List<String> words = List.of("Java", "Spring", "Boot");
List<Integer> lengths = words.stream()
.map(word -> word.length())
.toList();
System.out.println(lengths); // [4, 6, 4]
위 내용을 간단하게 표로 정리하면 다음과 같다.
인터페이스 | 입력 | 출력 | 설명 |
Predicate | T | boolean | 조건 검사 |
Consumer | T | void | 소비 (출력/저장 등) |
Supplier | 없음 | T | 공급 (생성/반환) |
Function<T, R> | T | R | 입력 → 출력 매핑 |
함수형 인터페이스가 하나의 동작만 가지는 이유
자바에서 함수형 인터페이스가 하나의 추상 메소드만 가지는 이유는 람다 표현식의 본질과 깊은 관련이 있다.
✅ 람다 표현식은 하나의 동작(함수)을 표현
람다식은 익명 함수(anonymous function)을 표현하는 방식이다. 즉, 하나의 기능(동작)을 전달하거나 변수처럼 사용할 수 있게 하기 위해 도입된 문법이다.
람다식을 컴파일러가 어떤 인터페이스로 변환하려면, 해당 인터페이스는 정확히 하나의 추상 메서드만 가지고 있어야 한다. 그래야 컴파일러가 확실하게 "아, 이 람다식은 이 인터페이스의 이 메서드를 구현하는 거구나!" 하고 추론할 수 있다.
❌ 만약 추상 메소드가 2개 이상이면?
람다식은 하나의 함수 본체만 갖기 때문에 어느 메서드를 구현하려는 건지 컴파일러가 알 수 없게 된다.
예시
@FunctionalInterface
interface InvalidInterface {
void doSomething();
void doAnother();
}
이렇게 되면 아래와 같은 람다식은 무엇을 구현하는지 모호해진다.
InvalidInterface obj = () -> System.out.println("Hello");
doSomething()인가? doAnother()인가?
명확하지 않기 때문에 컴파일 에러가 발생한다.
참고로, 여러개의 default, static 메소드는 가질 수 있다. 오로지 추상 메소드가 1개만 있으면 함수형 인터페이스로 볼 수 있다.