본문 바로가기
프로그래밍/Java

[java8] java의 주요 functional interface - function, supplier, consumer

by 사바라다 2020. 5. 9.

안녕하세요. 오늘은 java의 functional interface에 대해서 몇가지 알아보는 시간을 가지도록 하겠습니다. functional Interface는 java8에서 나와 주로 람다식으로 자주사용되고 있으며 함수형 패러다임을 달성할 수 있도록 하는데 도움을 주고 있습니다. 해당 interface들은 java.util.function 패키지에 모두 포함되어 있습니다.

오늘은 이런 functional interface와 이용에 대해서 알아보도록 하겠습니다.

Function

Function은 1개의 파라미터를 받아서 1개의 결과를 반환하는 funtional interface입니다. Function<T, R>로 사용할 수 있으며 T는 받는 파라미터, R은 반환하는 값입니다.

실제 인터페이스는 아래와 같습니다.

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function을 주로 사용하는 대표적인 예는 Stream에서 map를 들 수 있습니다. map은 Stream의 형태를 Stream로 변경하는 메서드입니다. 파라미터로 Function interface를 받습니다. 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);

여기서 T는 기존 Stream의 제네릭 타입이며 R은 변환하고 하는 타입입니다. 따라서 R은 어떠한 타입도 될 수 있습니다. 사용방법은 아래와 같습니다.

private Function<Integer, String> stringMap = integer -> String.valueOf(integer);

@Test
public void FunctionTest() {
    Stream<Integer> integerStream = Stream.of(1, 2);
    List<String> collect = integerStream.map(stringMap).collect(Collectors.toList());
    System.out.println("collect = " + collect);
}

저는 좀 더 명확하게 전달드리기 위해서 Functional interface를 별도로 구현하였습니다. 하지만 일반적으로 저정도는 map(String::valueOf) 정도로 처리할 수 있습니다.

Supplier

Supplier fucntional interface는 값을 생성하기 위해서 사용합니다. 함수형이기 때문에 lazy하게 처리할 수 있습니다. Supplier<T>의 형태를 가지며 T는 반환 값의 타입입니다. 해당 Interface는 아래와 같습니다.

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Supplier가 대표적으로 사용되는 것으로 Stream의 generate 메서드를 사용할 수 있습니다. generate는 Stream 객체를 만드는 메서드입니다.

/**
* Returns an infinite sequential unordered stream where each element is
* generated by the provided {@code Supplier}.  This is suitable for
* generating constant streams, streams of random elements, etc.
*
* @param <T> the type of stream elements
* @param s the {@code Supplier} of generated elements
* @return a new infinite sequential unordered {@code Stream}
*/
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
        new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}

generate는 Supplier<T>를 파라미터로 받으며 Supplier<T> 에서 반환된 T 값을 사용합니다. Supplier<T>는 선언되었을 때 바로 사용되는 것이 아니라 generate에서 사용될 때 값을 반환하는 형식으로 사용되기 때문에 이는 함수형 패러다임의 lazy를 따른다고 볼 수 있습니다.

@Test
public void SupplierTest() {
    int i = 5;

    Supplier<Integer> integerSupplier = () -> i * i;
    Optional<Integer> first = Stream.generate(integerSupplier).findFirst();
    System.out.println("first.get() = " + first.get());
}

Consumer

Supplier와는 반대로 Consumer interface는 단일 파라미터를 받아들이고 아무런 리턴값이 없는 functional interface입니다.. Consumer<T>로 사용하며 T는 받는 단일 객체의 타입 입니다.

인터페이스는 아래와 같습니다.

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

대표적으로 사용하는 곳은 foreach 구문입니다. for를 대신해서 list를 간단히 처리하고자 할때 foreach를 자주 사용합니다.

/**
* 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);
    }
}

foreach의 내부 로직은 이렇습니다. Consumer라는 functional interface를 구현한 로직을 for 구문으로 실행하는 것입니다. 아래는 foreach와 Consumer를 사용한 예제입니다. 물론 실전에서는 람다로 바로 사용하는것을 추천합니다.

@Test
public void ConsumerTest() {
        Consumer<Integer> consumer = integer -> System.out.println(integer); 
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        integers.forEach(consumer);
}

마무리

오늘은 이렇게 java8의 주요 functional interface 중 3가지를 알아보았습니다. 원래 6가지를 다루고 싶었지만 이야기가 길어지던 관계로 다음 포스팅에 3가지를 추가로 알아보도록 하겠습니다.

감사합니다.

참조

java8 function package-summary

https://www.baeldung.com/java-8-functional-interfaces

댓글