Programming Language/Java

[Java] predicate, consumer, supplier, function 이해하기 (함수형 인터페이스)

장쫄깃 2025. 6. 22. 15:06
728x90


들어가며


Java에서 Predicate, Consumer, Supplier, Functionjava.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개만 있으면 함수형 인터페이스로 볼 수 있다.

 

728x90