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

[java8] java의 주요 functional interface - Predicate, Operator

by 사바라다 2020. 5. 11.

안녕하세요. 오늘은 저번 포스팅에 이어서 functional interface를 알아보는 2번째 시간입니다. 오늘 알아볼 Functional Interface는 Predicate, Operate 2가지입니다.

Predicate

Predicate Interface는 T에대한 조건에 대해서 true / false를 반환하는 Fucntional Interface입니다. Predicate<T>로 사용되며 T는 파라미터입니다. 해당 파라미터에 대해서 true / false를 return 할 수 있도록 작성해주면 됩니다.

Predicate interface는 아래와 같습니다.

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

Predicate가 사용되는 가장 대표적인것은 Stream의 filter입니다. Stream에서 filter는 Stream의 요소중 통과할 요소와 제거할 요소를 걸러내는 작업을 해주는 함수형 명령어입니다.

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

위에 설명에도 나와있듯이 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입니다. UnaryOperator는 Functional Interface를 확장하였습니다. Function Interface는 T -> R 이며, UnaryOperator는 T -> T 입니다. 실제 코드에서도 아래와 같이 Funtion Interface를 확장해서 사용하고 있습니다.

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

UnaryOperator는 우리가 많이 사용하는 ArrayList의 메서드중 하나에서 찾아 볼 수 있었습니다. replaceAll 이라는 메서드입니다. 해당 메서드는 ArrayList의 elements들을 일괄로 특정 식을 통해서 병경하는 메서드 입니다. 아래는 해당 메서드의 내부 구조입니다.

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

iterator로 해당 list를 순회하면서 UnaryOperator의 수식을 각 element에 적용하는 코드를확인할 수 있었습니다. 해당 코드를 우리가 사용하기 위해서는 UnaryOperator Interface를 구현하여야하며 아래와 같이 작성할 수 있습니다. 아래의 코드의 결과는 list = [2, 4]로 출력될 것입니다.

@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입니다. BinaryOperator는 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> {
    ...
}

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

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

reduce의 는 위와같이 정의되어 있습니다. 그리고 아래와같이 사용할 수 있습니다. 첫번째(first) 파라미터는 이전 연산의 결과값입니다. 그리고 2번째(second) 파라미터는 새로 들어온 값입니다. 위에서 설명했다시피 로직은 실행되며 아래의 결과는 1 + 2 + 3 +4 + 5로 15가 나옵니다.

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

마무리

오늘은 저번시간에 이어서 2번째 functional interface에 대해서 알아보는 시간으로 Predicate와 UnaryOperator, 그리고 BinaryOperator에 대해서 알아보는 시간을 가졌습니다.

감사합니다.

참조

funcitonal oracle docs

댓글