본문으로 바로가기

목차

    1. 표준 함수형 인터페이스

    • Java 8부터 기본으로 제공하는 함수형 인터페이스
    • java.lang.funcation 패키지
    • 표준 함수형 인터페이스와 람다의 등장으로 기존 라이브러리의 많은 변화가 생겼습니다.
    • Function<T, R>, BiFunction<T, U, R>, UnaryOperator<T>, BinaryOperator<T>, Consumer<T>, Supplier<T>, Predicate<T>

    [표준 함수형 인터페이스 활용 예시]

    • ArrayList 클래스의 forEach 메소드를 보면  Consumer<T>를 매개 값으로 활용하고 있는 것을 알 수 있습니다.
    • Consumer<T> : T를 받아서 아무 값도 리턴하지 않는 함수형 인터페이스
    public class ArrayList<E> extends AbstractList<E>
    
        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int expectedModCount = modCount;
            final Object[] es = elementData;
            final int size = this.size;
            for (int i = 0; modCount == expectedModCount && i < size; i++)
                action.accept(elementAt(es, i));
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

     

    • 따라서 람다를 활용하여 편하게 ArrayList를 출력할 수 있습니다.
    public class Exe {
    
        public static void main(String[] args) {
        
            ArrayList<String> names = new ArrayList<>();
            names.add("B");
            names.add("D");
            names.add("A");
            names.add("C");
            
            names.forEach(name -> System.out.println(name));    
        }
    }

    2. Function<T, R>

    • 구현할 추상 메소드 = R apply(T t); 
    • T 타입을 받아서 R을 리턴

    [Function<T, R> 인터페이스]

    @FunctionalInterface
    public interface Function<T, R> {
    
        R apply(T t);
    
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }

     

    [사용 예시 1]

    • 람다 또는 익명 내부 클래스를 이용하여 구현하면 됩니다.
    • 람다가 훨씬 작성하기도 편하고 가독성도 좋음을 알 수 있습니다. 
    import java.util.function.Function;
    
    public class Test {
    
        public static void main(String[] args) {
        
        
            // 익명 내부 클래스로 구현
            Function<Integer, String> numberToStringInner = new Function<Integer, String>() {
                @Override
                public String apply(Integer integer) {
                    return integer.toString();
                }
            };
    
            // 람다를 활용하여 구현
            Function<Integer, String> numberToStringLambda = num -> Integer.toString(num);
    
            System.out.println(numberToStringInner.apply(10) + " : " + numberToStringInner.apply(10).getClass());
            System.out.println(numberToStringLambda.apply(10) + " : " + numberToStringLambda.apply(10).getClass());
        }
    }

     

    [사용 예시 2]

    • andThen(), compose()를 활용하여 조합해서 사용할 수도 있습니다.
    • andThen() = 현재 메소드를 실행 후 매개 변수로 받은 함수를 나중에 처리합니다.
    • compose() = 매개 변수로 받은 함수를 먼저 처리합니다. ( andThen()과 반대 )
    import java.util.function.Function;
    
    public class Test {
    
        public static void main(String[] args) {
    
            Function<Integer, Integer> plus10 = num -> num+10;
            Function<Integer, Integer> div2 = num -> num/2;
    
            System.out.println("plus10(10) : " + plus10.apply(10));
            System.out.println("div2(10) : " + div2.apply(10));
            System.out.println("plus10.andThen(div2).apply(10) : " + plus10.andThen(div2).apply(10)); // (10+10)/2
            System.out.println("plus10.compose(div2).apply(10) : " + plus10.compose(div2).apply(10)); // (10/2)+10
        }
    }

    3. BiFunction<T, U, R>

    • 구현할 추상 메서드 = R apply(T t, U u);
    • T, U를 받아서 R타입으로 반환하는 인터페이스

    [BiFunction<T, U, R> 인터페이스]

    @FunctionalInterface
    public interface BiFunction<T, U, R> {
    
        R apply(T t, U u);
    
        default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t, U u) -> after.apply(apply(t, u));
        }
    }

     

    [사용 예시]

    import java.util.function.BiFunction;
    
    public class Test {
    
        public static void main(String[] args) {
    
            BiFunction<Integer, Integer, String> addToString = (num1, num2) -> Integer.toString(num1 + num2);
            System.out.println(addToString.apply(1,2) + " : " + addToString.apply(1,2).getClass());
        }
    }

    4. UnaryOperator<T>

    • 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스
    • Function<T, R>의 특수한 형태로 Function<T, T>를 상속 받음, 따라서 apply()로 구현합니다. 

    [UnaryOperator<T> 인터페이스]

    @FunctionalInterface
    public interface UnaryOperator<T> extends Function<T, T> {
    
        static <T> UnaryOperator<T> identity() {
            return t -> t;
        }
    }

     

    [사용 예시]

     

    import java.util.function.UnaryOperator;
    
    public class Test {
    
        public static void main(String[] args) {
    
            UnaryOperator<Integer> plus50 = (i) -> i + 50;
            UnaryOperator<Integer> multiply2 = (i) -> i * 2;
    
            System.out.println(plus50.andThen(multiply2).apply(5));
        }
    }

    5. BinaryOperator<T>

    • 동일한 타입의 입력값 두 개를 받아 리턴하는 함수 인터페이스
    • BiFunction<T, U, R>의 특수한 형태로 BiFunction<T,T,T>를 상속받습니다. 따라서 apply()로 구현합니다. 

    [BinaryOperator<T> 인터페이스]

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

     

    [사용 예시]

    import java.util.function.BinaryOperator;
    
    public class Test {
    
        public static void main(String[] args) {
    
            BinaryOperator<String> introduce = (name, food) -> "My name is " + name + " I like " + food;
            System.out.println("introduce(veneas, chicken) : "+ introduce.apply("veneas", "chicken"));
        }
    }

    6. Consumer<T>

    • 구현할 추상 메소드 = void accept(T t);
    • T를 받아서 아무 값도(void) 리턴하지 않습니다.

    [Consumer<T> 인터페이스]

    @FunctionalInterface
    public interface Consumer<T> {
    
        void accept(T t);
    
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }

     

    [사용 예시]

    import java.util.Arrays;
    import java.util.List;
    import java.util.function.Consumer;
    
    public class Test {
    
        public static void main(String[] args) {
    
            Consumer<String> printName = System.out::println;
            List<String> fruits = Arrays.asList("AAPL", "MSFT", "GOOGL");
            Consumer<List<String>> lowerCase = list -> list.forEach(e -> System.out.print(e.toLowerCase() + " "));
    
            printName.accept("veneas");
            lowerCase.accept(fruits);
        }
    }

    7. Supplier<T>

    • 구현할 추상 메서드 = T. get();
    • 아무 값도 받지 않고 T를 반환합니다.

    [Supplier<T> 인터페이스]

    @FunctionalInterface
    public interface Supplier<T> {
    
        T get();
    }

     

    [사용 예시]

    import java.util.function.Supplier;
    
    public class Test {
    
        public static void main(String[] args) {
    
            Supplier<Integer> get10 = () -> 10;
            System.out.println("get10 : " + get10.get());
        }
    }

    8. Predicate<T>

    • 구현할 추상 메소드 = boolean test(T t);
    • T를 받아서 boolean(true, false)를 반환합니다.

    [Predicate<T> 인터페이스]

    @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);
        }
    
        @SuppressWarnings("unchecked")
        static <T> Predicate<T> not(Predicate<? super T> target) {
            Objects.requireNonNull(target);
            return (Predicate<T>)target.negate();
        }
    }

     

    [사용 예시]

    import java.util.function.Predicate;
    
    public class Test {
    
        public static void main(String[] args) {
    
            Predicate<String> containJava = s -> s.contains("Java");
            Predicate<Integer> isEven = (i) -> i%2 == 0;
            Predicate<Integer> moreThan5 = (i) -> i>5;
    
            System.out.println("containJava(Java) : "+ containJava.test("Java")); // true
            System.out.println("containJava(Python) : " + containJava.test("Python")); // false
            System.out.println("isEven(10) : " + isEven.test(10)); // true
            System.out.println("moreThan5.and(isEven).test(6) : " + moreThan5.and(isEven).test(6)); // true
            System.out.println("moreThan5.or(isEven).test(4) : " + moreThan5.or(isEven).test(3)); // false
        }
    }