함수형 인터페이스

Jun·2025년 5월 4일

고급 자바

목록 보기
3/7

자바 함수형 인터페이스와 람다 정리

1. 함수형 인터페이스와 제네릭

함수형 인터페이스에 제네릭을 사용하면 다음과 같은 이점이 있다:

  • 코드 재사용성이 증가한다.
  • 타입 안전성을 확보할 수 있다.

기존 방식인 Object 타입을 사용하는 ObjectFunction은 다양한 타입을 다룰 수 있지만, 매번 다운캐스팅이 필요하고, 이는 런타임에 에러 발생 가능성을 높인다.

반면 제네릭을 사용하면:

  • 컴파일 시점에 타입 체크가 이루어져 런타임 에러를 방지할 수 있다.
  • 타입에 따라 유연한 코드 작성이 가능하다.
// 기존 방식 (다운캐스팅 필요)
interface ObjectFunction {
    Object apply(Object input);
}

// 제네릭 방식 (타입 안전성 확보)
interface GenericFunction<T, R> {
    R apply(T input);
}

2. 람다와 타겟 타입

람다는 자체로 타입이 없고, 대입되는 함수형 인터페이스(타겟 타입)에 따라 타입이 결정된다.

  • 같은 람다식이라도 대입되는 인터페이스에 따라 타입이 달라진다.
  • 이미 특정 함수형 인터페이스로 결정된 람다는 다른 타입의 인터페이스로 변경할 수 없다.
@FunctionalInterface
interface FunctionA {
    int calculate(int x);
}

@FunctionalInterface
interface FunctionB {
    int operate(int x);
}

FunctionA fa = x -> x + 1;
// FunctionB fb = fa; // 오류 발생, 타입 불일치

3. 자바 기본 제공 함수형 인터페이스

자바는 다음과 같은 주요 함수형 인터페이스를 기본으로 제공한다:

  • Function<T, R>: 하나의 매개변수를 받아 결과를 반환.
  • Consumer<T>: 하나의 매개변수를 받아 처리만 하고 반환값은 없음.
  • Supplier<T>: 매개변수 없이 값을 반환.
  • Runnable: 매개변수와 반환값이 없는 작업 실행(주로 스레드 사용).

예시 코드:

Function<Integer, String> intToString = Object::toString;
Consumer<String> printer = System.out::println;
Supplier<Double> randomSupplier = Math::random;
Runnable runnable = () -> System.out.println("Hello, Runnable!");

4. 특화 함수형 인터페이스

자바는 특정 목적에 적합한 추가적인 특화 인터페이스를 제공한다:

  • Predicate<T>: 입력값을 받아 조건 검사 후 boolean 반환.
  • UnaryOperator<T>: 같은 타입의 단일 입력을 받아 같은 타입을 반환.
  • BinaryOperator<T>: 같은 타입의 두 입력을 받아 같은 타입을 반환.

추가로 2개의 매개변수를 다룰 수 있는 인터페이스들도 제공한다:

  • BiFunction<T, U, R>
  • BiConsumer<T, U>
  • BiPredicate<T, U>

기본형(primitive) 타입을 위한 전용 인터페이스 (IntPredicate, DoubleFunction 등)도 제공한다.

예시 코드:

Predicate<Integer> isEven = x -> x % 2 == 0;
UnaryOperator<Integer> square = x -> x * x;
BinaryOperator<Integer> sum = (a, b) -> a + b;

5. 문제 예시와 함수형 인터페이스 활용

자주 사용되는 예제들(filter, map, reduce)에서 직접 정의한 함수형 인터페이스를 자바가 제공하는 인터페이스로 대체하는 예를 살펴보았다.

가장 중요한 것은 코드의 입력과 반환 구조, 그리고 그 의도를 명확히 드러낼 수 있는 인터페이스를 선택하는 것이다.

  • 조건 검사는 Predicate
  • 단항 연산(입력과 반환 타입 동일)은 UnaryOperator
  • 이항 연산(입력 2개와 반환 타입 동일)은 BinaryOperator

결론

람다와 함수형 인터페이스를 적극 활용하면 코드가 간결하고 가독성이 높아진다. 제네릭을 함께 사용하면 추가적으로 재사용성타입 안전성까지 얻을 수 있다. 또한 자바에서 제공하는 다양한 기본 함수형 인터페이스를 활용하면 불필요한 인터페이스 생성을 피하고, 호환성 문제를 해결할 수 있다.

무엇보다 코드의 "의도를 명확하게 드러내는" 함수형 인터페이스를 선택하는 것이 핵심이다. 이로써 코드의 목적이 분명해지고, 유지보수성이 향상된다.

출처

김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍

profile
꾸준하게

0개의 댓글