저번 포스팅에서 못 다 정리한 Predicate 와 Operator에 대해 알아보자
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]
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
그동안 정리한걸 토대로 표로 만들어보았다.
종류 | 매개변수 | 반환값 | 특징 |
---|---|---|---|
Function | O | O | 주로 매개변수를 반환값 타입으로 변환 후 반환 |
Consumer | O | X | 추상메소드 제공 |
Supplier | X | O | |
Predicate | O | O | 주로 매개변수를 조사한 후 논리값 반환 역할 |
Operator | O | O | 주로 매개변수를 연산 후 결과값 반환 역할 |
이로써 Function, Consumer, Supplier, Predicate, Operator에 관해 모두 알아보았다.
이번 포스팅을 통해 사용방법을 알아보는 것도 좋았지만 내부로직까지 알아보면서 더 깊이있게 알게 되었다.