Java에서 기본적으로 제공해주는 함수형 인터페이스에 대해 공부해보려고 한다.
함수형 인터페이스는 java.util.function 패키지 내에 존재하고 많은 함수형 인터페이스들이 있지만 그 중에 자주 사용하는 몇 가지만 공부해본다.
(직접 java.util.function에 들어가보면 @FunctionalInterface 로 지정되있는 것을 확인 할 수 있다.)
package java.util.function;
import java.util.Objects;
/**
* Represents a function that accepts one argument and produces a result.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object)}.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
Function은 1개의 파라미터를 받아서 1개의 결과를 반환하는 Funtional Interface이다
Function<T, R>로 사용할 수 있으며 T 타입을 받아서 R 타입을 리턴하는 한다.
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
Function에 들어 있는 각 함수를 사용해 보겠습니다.
apply()
Function<Integer, Integer> sum10 = (i) -> i + 10;
System.out.println("sum10 : " + sum10.apply(new Integer("4"))); // 실행 되는 시점
sum10 : 14
andThen()
함수 조합용 메소드
인터페이스A.andThen(인터페이스B) 는 A를 하고나면 B실행
Function<Integer, Integer> sum10 = (i) -> i + 10;
Function<Integer, Integer> multi2 = (i) -> i * 2;
Function<Integer, Integer> result = multi2.andThen(sum10); // multi2 후 sum10를 하기로 정의
System.out.println("result : " + result.apply(new Integer("4"))); // 실행 되는 시점
result : 18
compose()
함수 조합용 메소드
인터페이스A.compose(인터페이스B) 는 A를 하기 전에 B실행
Function<Integer, Integer> sum10 = (i) -> i + 10;
Function<Integer, Integer> multi2 = (i) -> i * 2;
Function<Integer, Integer> result = multi2.compose(sum10); // multi2를 하기 전에 sum10를 하기로 정의
System.out.println("result : " + result.apply(new Integer("4"))); // 실행 되는 시점
result : 28
Function의 경우는 일반적으로 많이 사용하는 stream에 map에 해당한다.
map의 내부로직은 다음과 같다.
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Function<Integer, String> stringMap = integer -> String.valueOf(integer);
Stream<Integer> integerStream = Stream.of(1, 2);
List<String> intToStringList = integerStream.map(stringMap).collect(Collectors.toList());
System.out.println("intToStringList = " + intToStringList);
intToStringList = [1, 2]
BiFunction는 두 개의 값(T, U)를 받아서 R 타입을 리턴하는 함수 인터페이스
T, U, R 모두 다른 타입이 들어올 수 있다
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
사용 예제)
BiFunction<Integer, Integer, Integer> sum = (num1, num2) -> num1 + num2;
System.out.println("result1 : " + sum.apply(new Integer(3), new Integer(5)));
BiFunction<String, Integer, Integer> sumStringAndInteger = (str1, num1) -> Integer.parseInt(str1) + num1;
System.out.println("result2 : " + sumStringAndInteger.apply("3", 5));
BiFunction<String, String, Integer> sumStringToInteger = (str1, str2) -> Integer.parseInt(str1) + Integer.parseInt(str2);
System.out.println("result3 : " + sumStringToInteger.apply("3", "5"));
result1 : 8
result2 : 8
result3 : 8
Supplier Fucntional Interface는 값을 생성하기 위해서 사용한다.
Supplier의 형태를 가지며 T는 반환 값의 타입이다.
해당 Interface는 아래와 같습니다.
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
T get()
int num = 5;
Supplier<Integer> sumSupplier = () -> num + 10;
System.out.println(sumSupplier.get());
15
Supplier와는 반대로 Consumer interface는 단일 파라미터를 받아들이고 아무런 리턴값이 없는 Functional Interface이다.
Consumer로 사용하며 T는 받는 단일 객체의 타입이다
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
사용예제 )
Consumer<Integer> consumer = (num) -> System.out.println("num : " + num);
consumer.accept(7);
andThen()
함수 조합용 메소드
Consumer<Integer> consumerA = (num) -> System.out.println("num : " + num);
Consumer<Integer> consumerB = (num) -> System.out.println("num : " + num+10);
Consumer<Integer> consumerAafterB = consumerB.andThen(consumerA); //B후에A실행
consumerAafterB.accept(5);
num : 15
num : 5
Consumer가 stream에서 대표적으로 사용 되는 곳은 forEach이다.
forEach의 내부 로직은 이렇다. Consumer라는 functional interface를 구현한 로직을 for 구문으로 실행하는 것이다.
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
아래는 foreach와 Consumer를 사용한 예제이다 물론 실전에서는 람다로 바로 사용하는것을 추천한다.
Consumer<Integer> consumer = num -> System.out.println(num);
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5);
numList.forEach(consumer);
1
2
3
4
5
이번 포스팅에서는 Function과 Supplier, Consumer에 대해 알아보았다.
원래는 Predicate와 Operator에 대해서도 같이 정리하려고 했는데 생각보다 내용이 많아 다음 포스팅에서 추가적으로 알아보자.