자바에서 Collection 타입을 사용하다 보면 자연스럽게 스트림과 람다식을 사용하게 된다.
stream의 map 메서드와 filter 메서드, forEach 메서드에 사용된 람다식은 모두 같지만 map에서는 컴파일에러가 나지 않고, 나머지 메서드에서는 컴파일 에러가 난다.
이것은 스트림 람다식이 각 메서드에 알맞은 방식으로 작성돼야 한다는 것을 의미한다.
위 이미지에 사용된 메서드들을 자세히 알아보면 각각 괄호 안에 받는 것이 각각 다 다르다는 것을 알 수 있다.
Stream<T> filter(Predicate<? super T> predicate); //Predicate
<R> Stream<R> map(Function<? super T, ? extends R> mapper); //Function
void forEach(Consumer<? super T> action); //Consumer
즉, 메서드에 함수형 인터페이스
가 제공하는 타겟 타입에 맞추어 람다식을 사용해야 하는 것이다.
오늘은 이렇게 스트림 메서드에 사용되는 함수형 인터페이스에 대해 알아보자!
함수형 인터페이스에 대해 알아보면,
하나의 추상 메서드의 선언을 포함한 인터페이스
를 의미한다. 이것은, 추상 메서드
가 없거나 추상 메서드
선언을 2개 이상 가진 인터페이스는 함수형 인터페이스가 아니라는 것을 말한다. (default 메서드나 static 메서드는 제외)
함수형 인터페이스는 @FunctionalInterface
애노테이션을 붙여 선언한다.
Java 8에서는 Java.util.function 패키지에 많은 함수형 인터페이스가 존재하지만 이 호스팅에서는 중요한 몇 가지를 알아볼 것이다.
Supplier는 공급자라는 의미대로 인수없이 리턴 값을 반환하는 추상 메서드를 가지고 있다.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Optional<User> optionalUser = userRepository.findByDisplayName(displayName);
optionalUser.orElseThrow(() -> new RuntimeException("테스트"));
Consumer는 어떠한 인자를 받으나 리턴 값은 없는 추상 메서드를 가지고 있다.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//생략
}
testLists.forEach(m -> System.out.println(m));
Predicate 인터페이스는 인자를 평가하는 추상 메서드를 가지고 있다. 인수로 받은 것에 대해 조건에 맞으면 true, 틀리면 false를 리턴한다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//생략
}
List<Integer> testLists = List.of(1, 2, 3, 4, 5);
Predicate<Integer> integerPredicate = n -> n % 2 == 0;
testLists.stream().filter(integerPredicate).forEach(System.out::println);
Function 인터페이스는 받은 인수(T)를 가공하여 결과로 다른 리턴 값(R)을 리턴하는 추상 메서드를 가지고 있다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
//생략
}
List<Integer> testLists = List.of(1, 2, 3, 4, 5);
testLists.stream().map(n -> n * 2);
지금까지 대표적인 함수형 인터페이스에 대해서 알아봤다. 이 외에도 많은 함수형 인터페이스들이 존재하지만 나머지는 파생되는 개념이라 필요할 때 공부해보면 될 것이라고 생각한다.
참고: java.util.fucntion
Reference