이번 포스트에서는 람다표현식의 짝궁인 functional package의 interface들에 대해 살펴보자.
java.util.functional
일반적인 의도(함수)의 전달
java는 객체지향 언어이다. 전통적으로 객체지향 언어에서는 의도만 따로 전달할 수 있는 방법이 없었다. 의도를 전달하기 위해서는 의도가 속한 클래스를 객체화 하고 거기서 정확한 메서드의 이름과 형태(구조: 파라미터, 리턴타입)가 중요하다.
하지만 함수형 프로그래밍에서는 의도를 직접 전달 할 수 있다! 이 함수는 이름이 필요없고 내용(실행문)만 필요한데 일반적으로 함수의 형태(구조)는 고정되어있다.
표준 함수형 인터페이스
자바 진영에서는 이런 함수형 프로그래밍 기법이 탐나서 람다식을 도입했는데 매번 의도를 전달할 때마다 @FunctionalInterface를 만들고 사용하기가 매우 번거로웠다. 따라서 표준화된 함수의 구조를 정의하고 사용할 수 있게 준비했는데 이것을 표준 함수형 인터페이스라고 부른다.
이 세상의 그 많고 많은 함수의 구조 즉 파라미터와 리턴 타입을 표준화 시킬 수 있었을까? 어렵게 생각할 필요가 전혀 없다. 그냥 파라미터가 있고 없고, 그에 따라서 리턴 타입이 있고 없고이다.
종류
abstract method 특징
parameter
return
method
비고
java.lang.Runnable
X
X
void run()
Thread 용도
Consumer 계열
O
X
void accept(T)
- 소비자
Supplier 계열
X
O
T get()
- 공급자
Function 계열
O
O
T apply (R)
- 주로 parameter를 return 으로 매핑 : 새로운 타입으로 변환해서 리턴
Operation 계열
O
O
T XXX(R)
- 주로 parameter를 연산해서 return : 동일한 타입으로 연산 결과를 리턴
Predicate 계열
O
boolean
boolean test(T)
- parameter를 조사해서 return 결정
이중 Runnable 만 java.lang에 속해있고 나머지는 java.util.function 패키지에 속해있다. 이들은 모두 @FunctionalInterface이기 때문에 하나의 abstract 메서드만 가지고 있으며 각각 파라미터와 리턴타입이 있거나 없다. 이제 여러분의 의도 즉 실행문만 작성해서 전달해주면 된다.
활용 예
간단한 활용 예를 통해서 함수형 인터페이스를 사용해보자.
다음은 java.util.Iterable의 forEach라는 메서드이다. forEach는 Consumer 타입을 받는다.
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
Consumer는 파라미터를 받고 리턴하지 않는다. 즉 소비만 하는데 List의 요소 하나 하나씩을 넘겨받아서 무엇을 할 것인지 의도만 작성하면 된다.
List<String> list = Arrays.asList("Hello", "Java", "World");
list.forEach(str -> System.out.println(str));
다음으로 java.util.Collection에 있는 removeIf 라는 메서드를 살펴보자. 이 메서드는 Predicate 타입을 인자로 받는다.