함수형 인터페이스 종류(1)

gryoh·2022년 3월 7일
0

함수형 인터페이스 종류(1)

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<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>

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

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

Consumer

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에 대해서도 같이 정리하려고 했는데 생각보다 내용이 많아 다음 포스팅에서 추가적으로 알아보자.

0개의 댓글