Java

[Java] 함수형 인터페이스 예시 (Functional Interface)

ilot 2022. 9. 20. 13:24
package functional;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.junit.Test;
import org.junit.jupiter.api.DisplayName;

/*
 * 출처 : https://codechacha.com/ko/java8-functional-interface/
 */
public class FunctionalInterfaceTest {

    public interface FunctionalInterface {

        public abstract String returnParam(String text);

    }

    @Test
    @DisplayName("기존 익명 클래스를 이용한 방식")
    public void anonymousClassTest() {
        String target = "hello, anonymous class";

        FunctionalInterface fi = new FunctionalInterface() {
            @Override
            public String returnParam(String text) {
                return text;
            }
        };

        String result = fi.returnParam(target);
        assertThat(target).isEqualTo(result);
    }

    @Test
    @DisplayName("Functional Interface 시작")
    public void helloFunctionalInterface() {
        String target = "hello, functional interface";

        FunctionalInterface fi = (text) -> {
            return text;
        };

        String result = fi.returnParam(target);
        assertThat(target).isEqualTo(result);
    }

    /* 함수형 인터페이스를 람다식과 버무려서 사용할 수 있지만,
     * 매번 사용할 때마다 함수형 인터페이스를 정의하는 것은 불편할 수 있음.
     * 이를 위해 자바에서 라이브러리를 제공함.
     *
     * java.util.function 에 정의되어 있는 기본 함수형 인터페이스
     *   1. Runnable : 인자 없음, 리턴값 없음
     *   2. Supplier<T> : 인자 없음, T 타입의 변수를 리턴함
     *   3. Consumer<T> : T 타입의 변수를 인자로 받고, 리턴값 없음
     *   4. Function<T, R> : T 타입의 변수를 인자로 받고, R 타입의 변수를 리턴함
     *   5. Predicate<T> : T 타입의 변수를 인자로 받고, 결과를 boolean 으로 리턴함
     */


    @Test
    @DisplayName("1. Runnable : 인자 없음, 리턴값 없음")
    public void helloRunnable() {
        Runnable runnable = () -> System.out.println("hello, runnable");
        runnable.run();
    }

    @Test
    @DisplayName("2. Supplier<T> : 인자 없음, T 타입의 변수를 리턴함")
    public void helloSupplier() {
        String targetString = "hello, supplier";
        Supplier<String> getString = () -> targetString;
        assertThat(getString.get()).isEqualTo(targetString);
    }

    @Test
    @DisplayName("3. Consumer<T> : T 타입의 변수를 인자로 받고, 리턴값 없음")
    public void helloConsumer() {
        Consumer<String> printMyNameConsumer = name -> System.out.println(
            "My name is " + name + ".");
        printMyNameConsumer.accept("glory");
    }

    @Test
    @DisplayName("3. Consumer<T> : andThen 을 이용하면 두 개 이상의 Consumer 를 연속 실행할 수 있음")
    public void consumerAndThen() {
        Consumer<String> printMyNameConsumer = name -> System.out.println(
            "My name is " + name + ".");
        Consumer<String> printMyNicknameConsumer = nickname -> System.out.println(
            "My nickname is " + nickname + ".");
        printMyNameConsumer.andThen(printMyNicknameConsumer).accept("glory");
    }

    @Test
    @DisplayName("4. Function<T, R> : T 타입의 변수를 인자로 받고, R 타입의 변수를 리턴함")
    public void helloFunction() {
        int target = 3;
        Function<Integer, Integer> multiply = val -> val * 2;
        assertThat(multiply.apply(target)).isEqualTo(target * 2);
    }


    @Test
    @DisplayName("4. Function<T, R> : compose 로 두 Function 을 조합하여 새 Function 을 만들 수 있음.")
    public void functionCompose() {
        int target = 3;

        Function<Integer, Integer> multiply = (value) -> value * 2;
        Function<Integer, Integer> add = (value) -> value + 3;

        // 순서 : add -> multiply
        Function<Integer, Integer> composedFunction1 = multiply.compose(add);

        assertThat(composedFunction1.apply(target)).isEqualTo((target + 3) * 2);

        // 순서 : multiply -> add
        Function<Integer, Integer> composedFunction2 = add.compose(multiply);
        assertThat(composedFunction2.apply(target)).isEqualTo((target * 2) + 3);
    }

    @Test
    @DisplayName("5. Predicate<T> : T 타입의 변수를 인자로 받고, 결과를 boolean 으로 리턴함")
    public void helloPredicate() {
        Integer tar1 = 10;
        Integer tar2 = -10;
        Predicate<Integer> isBiggerThanFive = target -> target > 5;
        assertThat(isBiggerThanFive.test(tar1)).isTrue();
        assertThat(isBiggerThanFive.test(tar2)).isFalse();
    }

    @Test
    @DisplayName("5. Predicate<T> : and, or 메서드를 사용하여 다른 Predicate 와 동시에 검사 가능")
    public void predicateOrAnd() {
        Integer tar = 10;

        Predicate<Integer> isBiggerThanFive = target -> target > 5;
        Predicate<Integer> isLowerThanTwenty = target -> target < 20;
        Predicate<Integer> isOddNumber = target -> target % 2 == 1;

        assertThat(isBiggerThanFive.and(isLowerThanTwenty).test(tar)).isTrue();
        assertThat(isBiggerThanFive.or(isOddNumber).test(tar)).isTrue();
    }

}