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

gryoh·2022년 3월 7일
0

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

저번 포스팅에서 못 다 정리한 Predicate 와 Operator에 대해 알아보자

Predicate

Predicate Interface는 T에대한 조건에 대해서 boolean을 반환하는 Fucntional Interface이다.

Predicate로 사용되며 T는 파라미터이다

해당 파라미터에 대해서 true / false를 return 할 수 있도록 작성해주면 된다.

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

boolean test(T t)

Predicate<Integer> justOne = num -> num == 1;
System.out.println(justOne.test(1));
System.out.println(justOne.test(2));
true
false

and, or, negate

Predicate<Integer> justOne = num -> num == 1;
Predicate<Integer> justTwo = num -> num == 2;
Predicate<Integer> justOdd = num -> num % 2 != 0;
Predicate<Integer> justEven = num -> num % 2 == 0;

System.out.println("justTwo.and(justOdd).test(1) = " + justTwo.and(justOdd).test(1));
System.out.println("justTwo.and(justEven).test(2) = " + justTwo.and(justEven).test(2));
System.out.println("justTwo.or(justOdd).test(1) = " + justTwo.or(justOdd).test(1));
System.out.println("justTwo.or(justEven).test(2) = " + justTwo.or(justEven).test(2));
System.out.println("justTwo.negate().test(1) = " + justTwo.negate().test(1));
System.out.println("justTwo.negate().test(2) = " + justTwo.negate().test(2));
justTwo.and(justOdd).test(1) = false
justTwo.and(justEven).test(2) = true
justTwo.or(justOdd).test(1) = true
justTwo.or(justEven).test(2) = true
justTwo.negate().test(1) = true
justTwo.negate().test(2) = false

Predicate는 steam에서 대표적으로 filter에 쓰인다

filter의 내부로직은 다음과 같다.

/**
 * Returns a stream consisting of the elements of this stream that match
 * the given predicate.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
 *                  <a href="package-summary.html#Statelessness">stateless</a>
 *                  predicate to apply to each element to determine if it
 *                  should be included
 * @return the new stream
 */
Stream<T> filter(Predicate<? super T> predicate);
Predicate<Integer> justOne = num -> num == 1;
Stream<Integer> integerStream = Stream.of(1, 2);
Stream<Integer> filteredStream = integerStream.filter(justOne);

System.out.println("filtered Num = " + filteredStream.collect(Collectors.toList()));
filtered Num = [1]

Operator

UnaryOperator

UnaryOperator은 입력받은 파라미터 타입과 리턴 받는 타입이 동일한 Functional Interface입니다.

UnaryOperator는 Functional Interface를 확장하였습니다.

Function Interface는 T -> R 이며, UnaryOperator는 T -> T 입니다.

package java.util.function;

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

UnaryOperator는 우리가 많이 사용하는 ArrayList의 메서드중 하나에서 찾아 볼 수 있다.

replaceAll 이라는 메서드이다.

해당 메서드는 ArrayList의 elements들을 일괄로 특정 식을 통해서 병경하는 메서드 이다.

아래는 해당 메서드의 내부 구조이다.

/**
 * Replaces each element of this list with the result of applying the
 * operator to that element.  Errors or runtime exceptions thrown by
 * the operator are relayed to the caller.
 *
 * @implSpec
 * The default implementation is equivalent to, for this {@code list}:
 * <pre>{@code
 *     final ListIterator<E> li = list.listIterator();
 *     while (li.hasNext()) {
 *         li.set(operator.apply(li.next()));
 *     }
 * }</pre>
 *
 * If the list's list-iterator does not support the {@code set} operation
 * then an {@code UnsupportedOperationException} will be thrown when
 * replacing the first element.
 *
 * @param operator the operator to apply to each element
 * @throws UnsupportedOperationException if this list is unmodifiable.
 *         Implementations may throw this exception if an element
 *         cannot be replaced or if, in general, modification is not
 *         supported
 * @throws NullPointerException if the specified operator is null or
 *         if the operator result is a null value and this list does
 *         not permit null elements
 *         (<a href="Collection.html#optional-restrictions">optional</a>)
 * @since 1.8
 */
default void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final ListIterator<E> li = this.listIterator();
    while (li.hasNext()) {
        li.set(operator.apply(li.next()));
    }
}

사용예제)

List<String> nameList = Arrays.asList("jave", "jave8", "spring", "stream");
UnaryOperator<String> unaryOperator = s -> s.replaceAll("jave", "java");
System.out.println("unaryOperator 전 nameList = " + nameList);
nameList.replaceAll(unaryOperator);
System.out.println("unaryOperator 후 nameList = " + nameList);
unaryOperator 전 nameList = [jave, jave8, spring, stream]
unaryOperator 후 nameList = [java, java8, spring, stream]

BinaryOperator

BinaryOperator는 2개의 동일한 타입의 파라미터로 1개의 동일한 리턴 값을 가져오는 Functional Interface이다.

BinaryOperator는 BiFunction Interface를 확장한 Interface로 (T, U) -> R을 응용하여 (T, T) -> T로 이용한다.

package java.util.function;

import java.util.Objects;
import java.util.Comparator;

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
   
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

BinaryOperator는 stream에서 대표적으로 reduce에서 사용된다. reduce 내부 로직은 다음과 같다.

/**
 * Performs a <a href="package-summary.html#Reduction">reduction</a> on the
 * elements of this stream, using an
 * <a href="package-summary.html#Associativity">associative</a> accumulation
 * function, and returns an {@code Optional} describing the reduced value,
 * if any. This is equivalent to:
 * <pre>{@code
 *     boolean foundAny = false;
 *     T result = null;
 *     for (T element : this stream) {
 *         if (!foundAny) {
 *             foundAny = true;
 *             result = element;
 *         }
 *         else
 *             result = accumulator.apply(result, element);
 *     }
 *     return foundAny ? Optional.of(result) : Optional.empty();
 * }</pre>
 *
 * but is not constrained to execute sequentially.
 *
 * <p>The {@code accumulator} function must be an
 * <a href="package-summary.html#Associativity">associative</a> function.
 *
 * <p>This is a <a href="package-summary.html#StreamOps">terminal
 * operation</a>.
 *
 * @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
 *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
 *                    <a href="package-summary.html#Statelessness">stateless</a>
 *                    function for combining two values
 * @return an {@link Optional} describing the result of the reduction
 * @throws NullPointerException if the result of the reduction is null
 * @see #reduce(Object, BinaryOperator)
 * @see #min(Comparator)
 * @see #max(Comparator)
 */
Optional<T> reduce(BinaryOperator<T> accumulator);

reduce는 Stream의 elements들이 중첩하여 결과를 만드는 메서드이다. 즉 1,2,3 이라는 요소가 있고 이를 reduce로 더하면 (1 + 2), (3[1 + 2의 결과] + 3)로 결국 6의 결과를 내놓는다.

BinaryOperator<Integer> binaryOperator = (total, num) -> {
    System.out.println("total:" + total + ", num:" + num);
    return total + num;
};
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> reduce = integerStream.reduce(binaryOperator);
System.out.println("reduce = " + reduce.get());
total:1, num:2
total:3, num:3
total:6, num:4
total:10, num:5
reduce = 15

그동안 정리한걸 토대로 표로 만들어보았다.

종류매개변수반환값특징
FunctionOO주로 매개변수를 반환값 타입으로 변환 후 반환
ConsumerOX추상메소드 제공
SupplierXO
PredicateOO주로 매개변수를 조사한 후 논리값 반환 역할
OperatorOO주로 매개변수를 연산 후 결과값 반환 역할

이로써 Function, Consumer, Supplier, Predicate, Operator에 관해 모두 알아보았다.

이번 포스팅을 통해 사용방법을 알아보는 것도 좋았지만 내부로직까지 알아보면서 더 깊이있게 알게 되었다.

0개의 댓글