[JAVA8] Functional Interface

Better late than never·2023년 9월 11일
0

Java8 functional interface - function, supplier, consumer

Java8에서 나와 주로 람다식으로 자주 사용되고 있으며, 함수형 패러다임을 달성할 수 있도록 하는데 도움, 해당 interface들은 java.util.function 패키지에 모두 포함

Function

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

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

// EX
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

대표적인 EX

Stream에서 map, map은 Stream의 형태를 Stream로 변경하는 메서드. 파라미터로 Function interface를 받음

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

Supplier

Supplier functional interface는 값을 생성하기 위해서 사용, 함수형이기 때문에 lazy하게 처리할 수 있음. Supplier의 형태를 가지며 T는 반환 타입

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

// EX
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
        new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}

대표적인 EX

Stream의 generate, generate는 Stream 객체를 만드는 메서드로 Supplier를 파라미터로 받으며, Supplier에서 반환된 T값을 사용. Supplier는 선언되었을 떄 바로 사용되는 것이 아니라 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는 받는 단일 객체의 타입

@FunctionalInterface
public interface Consumer<T> {

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

//EX
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

대표적인 EX

foreach, for를 대신해 list를 간단히 처리하고자 할 떄 사용, Consumer라는 functional interface를 구현한 로직을 for구문으로 실행

@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);
}

Predicate

Predicate Interface는 T에 대한 조건에 대해서 true/false를 반환하는 Functional Interface, Predicate로 사용되며 T는 파라미터(해당 파라미터에 대해서 true/false를 return 할 수 있도록 작성

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

// EX
Stream<T> filter(Predicate<? super T> predicate);

대표적인 EX

Stream의 filter, filter는 Stream의 요소 중 통과할 요소와 제거할 요소를 걸러내는 작업을 해주는 함수형 명령어. 각 요소에 대해서 조건의 만족 유무에 따라서 제거

@Test
public void predicateTest() {
    Predicate<Integer> justOne = integer -> integer == 1;

        Stream<Integer> integerStream = Stream.of(1, 2);
        Stream<Integer> filteredStream = integerStream.filter(justOne);
        System.out.println("collect = " + filteredStream.collect(toList()));
}

UnaryOperator

UnaryOperator는 입력받은 파라미터 타입과 리턴 받는 타입이 동일한 Functional Interface, Functional Interface를 확장. Function Interface는 T → R이며, UnaryOperator는 T→T(실제 코드에서도 Function Interface를 확장해서 사용)

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    ...
}

// EX
default void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final ListIterator<E> li = this.listIterator();
    while (li.hasNext()) {
        li.set(operator.apply(li.next()));
    }
}

대표적인 EX

ArrayList의 메서드 중 replaceAll, ArrayList의 elements들을 일괄로 특정 식을 통해서 변경하는 메서드. iterator로 해당 list를 순회하면서 UnaryOperator의 수식을 각 element에 적용하는 코드를 확인할 수 있고, 해당 코드를 사용하기 위해 UnaryOperator Interface를 구현해야 함

@Test
public void unaryOperatorTest() {
    UnaryOperator<Integer> doubleOp = i -> i * 2;
    List<Integer> list = Arrays.asList(1, 2);
    list.replaceAll(doubleOp);
    System.out.println("list = " + list);
}

BinaryOperator

BinaryOperator는 2개의 동일한 타입의 파라미터로 1개의 동일한 리턴 값을 가져오는 Functional Interface, BiFunction Interface를 확장한 interface로 (T, U) → R을 응용하여 (T, T) → T로 이용

@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);
}

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
    ...
}

// EX
Optional<T> reduce(BinaryOperator<T> accumulator);

대표적인 EX

Stream의 reduce, Stream의 elements들이 중첩하여 결과를 만드는 메서드. 첫번쨰(first)파라미터는 이전 연산의 결과값이고 2번째(second)파라미터는 새로 들어온 값

@Test
public void binaryOperatorTest() {
    BinaryOperator<Integer> operator = (first, second) -> first + second;
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    Optional<Integer> reduce = integerStream.reduce(operator);
    System.out.println("reduce = " + reduce.get());
}

0개의 댓글