티스토리 뷰

Java

자바의 function interface

파미페럿 2022. 2. 4. 16:29

자바로 코딩을 하다보면 자바 라이브러리 함수들에서 아래와 같이 매개변수로 함수를 넣으라는 함수를 마주하게 된다.

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

 

보면 Predicate, Function, Supplier, Consumer, Operator이 있는 것 같은데 이들은 각각 무엇을 의미하는걸까?

(참고로 이들은 java.util.function에 존재한다.)

 

Predicate<T>

기본 형태는 아래와 같으며 boolean을 반환하는 함수를 의미한다. Predicate<T>를 사용하는 예로 스트림의 filter()를 들 수 있다.

@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는 하나의 매개변수를 받아서 해당 매개변수를 통해 true/false 값을 도툴한다.

    @Test
    public void spplierTest() {
       List<String> list = Arrays.asList("cat", "rabbit", "ferret", "dob");

       list.stream().filter((animal) -> animal.length() > 3);
    }

 

Predicate안에는 and, or 함수가 있어 명시적으로 조건을 and, or로 조합할 수 있다.

    @Test
    public void spplierTest() {
       List<String> list = Arrays.asList("cat", "rabbit", "ferret", "dob");

       Predicate<String> predicate1 = (animal) -> animal.length() > 3;
       Predicate<String> predicate2 = (animal) -> animal.startsWith("r");
       Predicate<String> predicate3 = (animal) -> animal.startsWith("c");

       list.stream().filter(predicate1.and(predicate2));
       list.stream().filter(predicate1.or(predicate3));
    }

 

Function<T, R>

Function은 매개변수를 하나 받아서 값을 반환하는 함수형 인터페이스이다. 스트임의 map() 함수를 예로 들수 있다.

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
	...
}

 

Function은 기본 타입 Double, Int, Long을 타입으로 사용하는 별도 Function들을 제공한다. 여기서 제공하지 않는 타입은 Function<T, R>을 이용해서 정의하면 된다.

 

예를 들어 스트림 map()을 아래와 같이 사용하던 것을 풀어보면

    @Test
    public void spplierTest() {
       List<String> list = Arrays.asList("cat", "rabbit", "ferret", "dob");

        System.out.println(list.stream().map(animal -> animal + "!").collect(Collectors.toList()));
        // [cat!, rabbit!, ferret!, dob!]
    }

 

아래와 같이 그 안에서 Function<String, String>을 매개변수로 받아서 사용했다는 것을 알 수 있다.

    @Test
    public void spplierTest() {
       List<String> list = Arrays.asList("cat", "rabbit", "ferret", "dob");

       Function<String, String> function = animal -> animal + "!";

       System.out.println(list.stream().map(function).collect(Collectors.toList()));
    }

 

Supplier<T>

Supplier<T>는 매개변수가 없이 값을 반환하는 함수형 인터페이스이다.

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

 

간단히 표현해보면 아래와 같다.

Supplier<String> supplier = () -> { return "Monday!!!!"; };

 

어떤 값을 반환하느냐에 따라를 여러 가지 Supplier가 존재하지만 이것들은 정확히는 Supplier<T>의 하위클래스들이 아닌 별도의 클래스이다.

BooleanSupplier, DoubleSupplier, IntSupplier, LongSupplier, BooleanSupplier가 존재한다.

 

 

그 밖의 타입들은 Supplier<T>를 통해서 표현이 가능하다.

Supplier<T>와 XXXSupplier의 안을 보면 매개변수가 없이 값을 반환하는 함수 하나만 존재한다.

 

 

Spplier<T>를 사용하는 방법은 아래와 같다. 단순히 특정 연산을 통해 값을 반환하는 함수를 변수처럼 사용한다고 보면된다. 함수의 반환값 자체를 얻는 것은 Supplier<T>에 해당 T타입으로 반환하는 값으로 반환한도록 정의를 한 후 get()을 사용하면 된다. 물론 T로 정의해놓은 타입이 아닌 다른 타입을 반환하게 하면 Supplier<T>에서 값을 반환하는 부분에서 에러가 발생한다. 컴파일 에러가 발생한다. XXXSupplier는 아예 함수 이름 밑 getXXX()함수로 타입을 정의해놓고 있어서 명확한 기본형을 반환할 때 확실히 할 수 있다.

    @Test
    public void spplierTest() {
        Supplier<String> supplier = () -> { return "Monday!!!!"; };

        System.out.println(supplier.get());
    }

 

Consumer<T>

consumer<T>는 매개변수 하나를 받고 아무 값도 반환하지 않는 함수형 인터페이스이다. 

@FunctionalInterface
public interface Consumer<T> {

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

 

스트림이나 컬렉션의 forEach에서 Consumer<T>를 받아서 사용한다.

    @Test
    public void spplierTest() {
        List<String> list = Arrays.asList("cat", "rabbit", "ferret", "dob");

        list.forEach(animal -> System.out.println(animal) );
    }

 

위 코드를 Consumter<T>로 풀어쓰면 아래와 같다.

    @Test
    public void spplierTest() {
        List<String> list = Arrays.asList("cat", "rabbit", "ferret", "dob");
        Consumer<String> consumer = animal -> System.out.println(animal);

        list.forEach(consumer);
    }

 

XXXOperator

Operator<T>는 제공하지 않고 XXXOperator들을 제공한다. XXX는 타입이름을 의미하고 Operator는 해당 타입의 값 두 개를 매개변수로 받아서 로직을 수행항 결과 값을 같은 타입으로 반환한다.

@FunctionalInterface
public interface DoubleBinaryOperator {
    /**
     * Applies this operator to the given operands.
     *
     * @param left the first operand
     * @param right the second operand
     * @return the operator result
     */
    double applyAsDouble(double left, double right);
}

 

XXXUnaryOperator도 있는데 이 함수형 인터페이스들은 하나의 해당 타입의 값 한 개를 매개변수로 받아서 로직을 수행한 결과 값을 같은 타입으로 반환한다.

@FunctionalInterface
public interface DoubleUnaryOperator {

    /**
     * Applies this operator to the given operand.
     *
     * @param operand the operand
     * @return the operator result
     */
    double applyAsDouble(double operand);
    ...
}

 

 function interface는 언제 사용하는가?

우선 위의 코드들을 보면 매개변수로 함수를 넘겨야할 때 함수형 인터페이스들을 사용했다. 기본적으로 함수형 인터페이스를 넘기는 것은 어떤 기능을 할지 모르는 함수가 넘어올 수 있어 사용을 지향하는 것이 좋지만 자바 라이브러리 같은 경우는 함수형 인터페이스를 넘겨 받아서 실행을 시키도록 되어 있다. 이럴 경우는 함수형 인터페이스를 사용해야한다.

 

그렇다면 그런 경우가 아니라면 함수형 인터페이스를 사용할 필요가 있을까라고 묻는다면 코드의 간결성을 이야기할 수 있다. 함수형 인터페이스를 매개변수로 필요로 하는 함수 말고도 특히 Supplier<T>의 경우는 실행 시점에 따라 값이 다르게 반환되는 경우 유용하게 사용될 수 있다. 그렇다면 이를 함수로 만들어도 되지 않을까? 싶기도 한데 복잡하고 긴 로직이 아닐 경우 함수로 만드는 대신 Supplier<T>로 만드는게 코드를 좀 더 간경하게 만들 수 있다.

    @Test
    public void spplierTest() {
        Supplier<Long> supplier = () -> { return System.currentTimeMillis() % 2; };

        System.out.println(supplier.get()); // 1
        System.out.println(supplier.get()); // 0
    }

 

 

 

 

 

✋ Predicate

https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html

 

Predicate (Java Platform SE 8 )

Returns a composed predicate that represents a short-circuiting logical OR of this predicate and another. When evaluating the composed predicate, if this predicate is true, then the other predicate is not evaluated. Any exceptions thrown during evaluation

docs.oracle.com

 

✋ Function

https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html

 

Function (Java Platform SE 8 )

 

docs.oracle.com

 

✋ Supplier

https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html

 

Supplier (Java Platform SE 8 )

Represents a supplier of results. There is no requirement that a new or distinct result be returned each time the supplier is invoked. This is a functional interface whose functional method is get().

docs.oracle.com

 

✋ Consumer

https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html

 

Consumer (Java Platform SE 8 )

andThen default Consumer  andThen(Consumer  after) Returns a composed Consumer that performs, in sequence, this operation followed by the after operation. If performing either operation throws an exception, it is relayed to the caller of the composed op

docs.oracle.com

 

✋ BinaryOperator<T>

https://docs.oracle.com/javase/8/docs/api/java/util/function/BinaryOperator.html

 

BinaryOperator (Java Platform SE 8 )

Represents an operation upon two operands of the same type, producing a result of the same type as the operands. This is a specialization of BiFunction for the case where the operands and the result are all of the same type. This is a functional interface

docs.oracle.com

 

✋ UnaryOperator<T>

https://docs.oracle.com/javase/8/docs/api/java/util/function/UnaryOperator.html

 

UnaryOperator (Java Platform SE 8 )

Represents an operation on a single operand that produces a result of the same type as its operand. This is a specialization of Function for the case where the operand and result are of the same type. This is a functional interface whose functional method

docs.oracle.com

 

 

 

 

 

반응형

'Java' 카테고리의 다른 글

어노테이션 중복 정의를 가능하게 하는 @Repeatable  (0) 2022.02.17
Build Gradle vs IntelliJ IDEA  (1) 2022.02.10
직렬화 프록시 패턴이란  (0) 2022.01.28
null 처리를 도와주는 Optional<T>  (0) 2022.01.14
Kryo serializer  (0) 2022.01.03
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함