본문 바로가기
language, framework, library/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

반응형

댓글