이전 포스트에서 함수형 인터페이스에 대해서 살펴봤습니다. 이번 포스트에서는 자바에서 기본적으로 제공하는 함수형 인터페이스를 살펴보겠습니다. 기본 API 에서 함수형 인터페이스는 java.util.function
패키지에 정의되어 있습니다.
- Functions
- Suppliers
- Consumers
- Predicates
- Operators
- Legacy Functional Interfaces
함수형 인터페이스 Functional Interface
이전 포스트에서 함수형 인터페이스에 대해 살펴봤습니다. 함수형 인터페이스는 컴파일러가 람다의 타입을 추론할 수 있도록 정보를 제공하는 역할을 합니다. 이 함수형 인터페이스는 단 하나의 추상 메소드(Single Abstract Method, SAM)만을 가질 수 있습니다.[1]
함수형 인터페이스에는 @FunctionalInterface
라는 어노테이션을 붙일 수 있습니다. @FunctionalInterface
는 개발자들에게 해당 인터페이스가 함수형 인터페이스라는 것을 알려주고 컴파일러가 SAM 여부를 체크할 수 있도록 합니다.
1 |
|
Functions
기본 형태
가장 기본적인 형태로 특정 오브젝트를 받아서 특정 오브젝트를 리턴하는 메소드 시그니쳐입니다. <T>
는 매개변수의 타입이고, <R>
은 리턴 타입입니다.
1 |
|
간단한 예제를 살펴보겠습니다. value2
를 계산하는 과정에서 스태틱 메소드가 아님에도 String::length
를 사용했는데 이는 참조할 인스턴스가 없기 때문입니다. 자세한 내용은 Java 8 Lambda (3) 메소드 참조 를 참고하세요.
1 | // computeIfAbsent 메소드 시그니쳐 |
Function
에는 compose
라는 디폴트 메소드가 있어 메소드를 순서대로 실행시킬 수 있습니다.
1 | // before 메소드 실행 후 결과를 받아서 현재 메소드 실행 |
다음과 같이 사용할 수 있습니다.
1 | Function<Integer, String> intToString = Objects::toString; |
andThen
은 compose
와 반대 역할을 합니다. 현재 메소드를 실행 후 매개변수로 받은 람다를 실행합니다.
1 | default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { |
다음은 간단한 예제입니다.
1 | Function<String, String> upperCase = v -> v.toUpperCase(); |
마지막으로 자신의 값을 그대로 리턴하는 스태틱 메소드 identity
가 있습니다.
1 | static <T> Function<T, T> identity() { |
간단한 예제입니다.
1 | String abc = Function.identity().apply("abc"); |
기본형 타입 관련
double
, int
, long
기본 타입에 대해서는 따로 함수형 인터페이스를 제공합니다. 이를 이용하면 제네릭 타입을 하나 줄일 수 있습니다.
IntFunction, LongFunction, DoubleFunction
매개변수 타입은 함수별로 지정되어 있고, 리턴 타입만 제네릭으로 받는 형태입니다.
1 |
|
ToIntFunction, ToLongFunction, ToDoubleFunction
반대로 리턴 타입은 함수별로 지정되어 있고, 매개변수 타입을 제네릭으로 받는 형태입니다.
1 |
|
간단한 예제를 만들어보면 다음과 같이 될 겁니다.
1 | public static int calculateStrSum(List<String> strList, ToIntFunction<String> function) { |
물론 duble
, int
, long
외의 타입에 대해서도 만들어 사용할 수 있습니다.
1 |
|
매개변수가 두 개인 경우
위에서 살펴 본 람다는 하나의 매개변수를 갖거나 매개변수가 없는 경우입니다. 두 개의 매개변수를 받는 람다는 Bi
키워드가 들어간 람다를 사용합니다.
1 |
|
- BiFunction : 두 개의 매개변수를 가지고 리턴 타입은
void
- ToIntBiFunction : 두 개의 매개변수를 가지고 리턴 타입은
int
- ToLongBiFunction : 두 개의 매개변수를 가지고 리턴 타입은
long
- ToDoubleBiFunction : 두 개의 매개변수를 가지고 리턴 타입은
double
1 | // 맵 내에 요소들에 대해 조건에 따라 값을 바꾸는 메소드 |
Suppliers
매개변수를 받지 않고 특정 타입의 결과를 리턴하는 함수형 인터페이스입니다.
1 |
|
이를 활용하면 값을 그냥 전하는 것이 아니라, 중간에 로직을 추가해서 전달할 수 있습니다.
1 | public double squre(double d) { |
위 예제는 단순히 제곱을 하는 메소드입니다. Supplier 를 이용해서 단순히 값을 가져오기 전에 실행될 로직을 추가할 수 있습니다. 예제에서는 Guava[2] 를 이용해서 1,000ms 딜레이를 줬습니다.
1 | public double squareLazy(Supplier<Double> lazyValue) { |
위에서 살펴본 바와 비슷하게 Supplier 도 기본형 리턴 타입에 따라 제네릭 없이 사용할 수 있습니다.
- BooleanSupplier
- DoubleSupplier
- LongSupplier
- IntSupplier
1 | public double squareLazy(DoubleSupplier lazyValue) { |
Consumers
Supplier 와는 반대로 Consumer 는 매개변수를 받고 리턴하지는 않습니다. Supplier 가 매개 변수 없이 특정 타입을 리턴하는 '공급자’이고 Consumer 가 리턴 타입 없이 매개변수를 받아 처리하는 '소비자’로 해석할 수 있으니 쉽게 구분하실 수 있을 것 같습니다.
1 |
|
forEach
메소드를 이용한 간단한 예제를 살펴보겠습니다.
1 | default void forEach(Consumer<? super T> action) { ... } |
매개변수에 따라 구분해서 사용할 수 있습니다.
- IntConsumer
- LongConsumer
- DoubleConsumer
또한 매개변수가 두 개인 Bi
버전도 있습니다.
1 | default void forEach(BiConsumer<? super K, ? super V> action) { ... } |
BiConsumer
도 타입에 따라 구분해서 사용할 수 있습니다.
- ObjDoubleConsumer
- ObjIntConsumer
- ObjLongConsumer
Predicates
특정 타입의 매개변수를 받아 boolean
값을 리턴하는 함수형 인터페이스입니다.
1 |
|
Stream API 의 filter
메소드와 함께 사용하는 예제입니다.
1 | // Stream 클래스의 filter 메소드 |
다른 메소드도 하나씩 살펴볼까요? and
, or
메소드는 다른 Predicate 람다와 함께 조합해서 사용이 가능합니다. negate
메소드는 조건을 반전시키고, 마지막으로 스태틱 메소드인 isEqual
을 이용해 매개변수와 동일한지 판단하는 람다를 생성할 수 있습니다.
1 | // [Angela] |
다른 함수형 인터페이스와 마찬가지로 매개변수 타입에 따라서 구분해서 사용할 수 있습니다.
- IntPredicate
- DoublePredicate
- LongPredicate
Operators
Operator 는 하나의 매개변수를 받고 동일한 타입을 리턴하는 함수형 인터페이스입니다. Function 인터페이스를 상속하고 있습니다. 여기에는 매개변수의 개수에 따라서 두 가지가 있습니다.
- UnaryOperator : 매개변수 하나
- BinaryOperator : 매개변수 둘
UnaryOperator
1 |
|
예제에서 replaceAll
은 해당 요소를 바꿔치기하는 것으로 동일한 리턴 타입이 필요합니다. replaceAll
메소드는 리턴 타입이 void
지만 람다를 이용해서 각 요소를 바꿔치기 할 수 있습니다.
1 | default void replaceAll(UnaryOperator<E> operator) { ... } |
매개변수 타입에 따라 구분해서 사용할 수 있습니다.
- DoubleUnaryOperator
- IntUnaryOperator
- LongUnaryOperator
BinaryOperator
1 |
|
다음 예제는 reduce
메소드를 이용해서 값들의 합을 구하는 예제입니다.
1 | // 초기값을 가지고 모든 요소를 하나의 값으로 만드는 로직을 수행 |
여기에서 넘겨지는 람다는 수학에서의 결합법칙(associative) 을 만족해야 합니다.
1 | // 결합법칙은 연산 순서가 바뀌어도 동일한 결과가 나오는 것을 의미 |
다른 인터페이스와 마찬가지로 매개변수 타입에 따라 여러 버전이 있습니다.
- DoubleBinaryOperator
- IntBinaryOperator
- LongBinaryOperator
기존 API 속 함수형 인터페이스
기존의 Runnable
과 Callable
인터페이스도 함수형 인터페이스로 변경되었습니다.
1 |
|
따라서 다음과 같이 람다를 사용해서 심플하게 코드를 작성할 수 있습니다.
1 | Thread t = new Thread(() -> System.out.println("Hello from another Thread")); |
요약
이번 포스트에서 살펴 본 함수형 인터페이스들을 정리해보겠습니다. 세부 타입에 따라서 인터페이스가 많지만 제네릭을 이용해서 간단하게 사용하시면 되겠습니다.
Function<T, R>
: 매개변수 하나를 받아서 특정 타입의 값을 리턴- 기본형 타입 매개변수를 받아 특정 타입의 값을 리턴
IntFunction<R>
LongFunction<R>
DoubleFunction<R>
- 특정 타입 값을 받아 기본형 타입을 리턴
ToIntFunction<T>
ToLongFunction<T>
ToDoubleFunction<T>
BiFunction<T, U, R>
: 매개변수가 두 개이고 특정 타입의 값을 리턴- 매개변수가 두 개이고 기본형 타입을 리턴
ToIntBiFunction<T, U>
ToLongBiFunction<T, U>
ToDoubleBiFunction<T, U>
- 기본형 타입 매개변수를 받아 특정 타입의 값을 리턴
Supplier<T>
: 매개변수 없이 특정 타입의 값을 리턴- 매개변수 없이 기본형 리턴 타입
BooleanSupplier
DoubleSupplier
LongSupplier
IntSupplier
- 매개변수 없이 기본형 리턴 타입
Cousumer<T>
: 매개변수를 받기만 하고 리턴하지는 않음- 매개변수 기본 타입
IntConsumer<T>
LongConsumer<T>
DoubleConsumer<T>
BiConsumer<T, U>
: 매개변수가 두 개인 경우- 매개변수 중 하나의 타입이 기본 타입인 경우
ObjDoubleConsumer<T>
ObjIntConsumer<T>
ObjLongConsumer<T>
- 매개변수 기본 타입
Predicate<T>
: 매개변수를 받아boolean
값을 리턴- 매개변수 기본 타입
intPredicate
DoublePredicate
LongPredicate
- 매개변수 기본 타입
UnaryOperator<T>
: 하나의 매개변수를 받아 동일한 타입의 값을 리턴BinaryOperator<T>
: 동일한 타입의 매개변수를 두 개 받아 동일한 타입의 값을 리턴- 기본형 타입
DoubleBinaryOperator
IntBinaryOperator
LongBinaryOperator
- 기본형 타입
- 기존 API 속 함수형 인터페이스
Runnable
Callable
참고
Related Posts
- Java Lambda (1) 기본
- Java Lambda (2) 타입 추론과 함수형 인터페이스
- Java Lambda (3) 메소드 참조
- Java Lambda (4) 기본으로 제공되는 함수형 인터페이스
- Java Lambda (5) 변수 범위
- Java Lambda (6) 예외 처리
- Java Lambda (7) 람다와 클로저
- 1.디폴트 메소드와 스태틱 메소드는 추상 메소드가 아니기 때문에 여러 개 있어도 상관없습니다. ↩
- 2.Guava : Google Core Libraries for Java ↩