First Class Citizon

  • First Class Citizon는 아래와 같은 속성들을 모두 만족해야 한다.

    • 변수에 값을 할당 할 수 있어야 한다.
    • 함수의 파라미터로 넘겨줄 수 있어야 한다.
    • 함수의 반환값이 될 수 있어야 한다.
  • Java에 method는 위 조건 모두를 만족하지 않음으로 Java에서 method는 일급객체가 아니고 때문에 java는 함수형 프로그래밍 언어가 아니다

  • Java8에서는 함수를 일급객체처럼 다룰 수 있게 함수형 인터페이스를 제공한다. 함수형 인터페이스란 단 하나의 추상 메소드만 가지는 인터페이스를 의미한다.(static함수는 여러 개 가질 수 있다.)

  • 함수형 인터페이스를 이용함으로서 객체를 선언하고 오버라이딩하고... 이런 노이즈들을 없애고 단순히 람다식 함수를 통해 코드를 간결하게 작성 할 수 있다.

  • 계산기 if - else 문없이 구현하기

    @FunctionalInterface
    interface Calculation {
      int calculate(final int num1, final int num2);
    }
    
    class FpCalculatorService {
      public int calculate(final Calculation calculation, final int num1, final int num2) {
          return calculation.calculate(num1, num2);  
      }
    }
    final FpCalculatorService fpCalculatorService = new FpCalculatorService();
        final Calculation addition = (i1, i2) -> i1 + i2;
        System.out.println("additon: " + fpCalculatorService.calculate(addition, 11, 4));
        System.out.println("subtraction: " + fpCalculatorService.calculate((i1, i2) -> i1 - i2, 11, 1));
    
    • 함수형 인터페이스를 통해 객체를 만들어서 overriding 할 필요없이 람다식만으로 파라미터로 넘겨준다.

    • 만약 함수형 인터페이스를 사용하지 않을 경우 new Calcautioan () { @Override ~~} 와 같이 파라미터로 넣어 주어야 한다.

함수형 인터페이스

  • Function

    • Function<T, R> 형태로 T는 파라미터 타입, R은 리턴타입이다. 추상메소드 apply() 를 가진다.

      • 파라미터를 변형없이 그대로 리턴하는 Function을 Identity Function이라고 부른다. 이게 왜 필요한지는 뒤에서 애기할 예정
    • public class FunctionExamples {
      
        private static void functionExamples() {
      
          final Function<String, Integer> toInt = value -> Integer.parseInt(value);
      
          final Integer number = toInt.apply("100");
          System.out.println(number);
      
          final Function<Integer, Integer> identity = Function.identity(); // Identity Function
          final Function<Integer, Integer> identity2 = t -> t; // Identity Function
      
          System.out.println(identity.apply(999));
          System.out.println(identity2.apply(999));
        }
      
        public static void main(final String[] args) {
          functionExamples();
        }
  • Predicate

    • Predicate<T, Boolean> 형태로, boolean을 리턴하는 함수형 인터페이스다. 추상메소드 test() 를 가진다.

      public class PredicateExamples {
      
        private static void predicateExamples() {
      
          final Predicate<Integer> isPositive = i -> i > 0;
      
          System.out.println(isPositive.test(1));
          System.out.println(isPositive.test(0));
          System.out.println(isPositive.test(-1));
      
          final List<Integer> numbers = Arrays.asList(-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5);
      
          final List<Integer> positiveNumbers = new ArrayList<>();
          for (final Integer num : numbers) {
            if (isPositive.test(num)) {
              positiveNumbers.add(num);
            }
          }
          System.out.println("positive integers: " + positiveNumbers);
      
          final Predicate<Integer> lessThan3 = i -> i < 3;
          final List<Integer> numbersLessThan3 = new ArrayList<>();
          for (final Integer num : numbers) {
            if (lessThan3.test(num)) {
              numbersLessThan3.add(num);
            }
          }
          System.out.println("less than 3: " + numbersLessThan3);
      
          System.out.println("positive integers: " + filter(numbers, isPositive));
          System.out.println("less than 3: " + filter(numbers, lessThan3));
      
        }
      
        // Steam API에서는 보다 간단하게 가능
        private static <T> List<T> filter(final List<T> list, final Predicate<T> filter) {
          final List<T> result = new ArrayList<>();
          for (final T input : list) {
            if (filter.test(input)) {
              result.add(input);
            }
          }
          return result;
        }
      
        public static void main(final String[] args) {
          predicateExamples();
        }
      
      }
  • Consumer

    • Consumer는 < T > 형태로 리턴이 없는 형태이다. 추상메소드 accept() 를 가진다.

      public class ConsumerExamples {
      
        private static void consumerExamples() {
      
          final Consumer<String> print = value -> System.out.println(value);
          print.accept("Hello");
      
          final Consumer<String> greetings = value -> System.out.println("Hello " + value);
          greetings.accept("World");
          greetings.accept("Kevin");
      
        }
      
        public static void main(final String[] args) {
          consumerExamples();
        }
      
      }
  • Supplier

    • Supplier는 < R > 형태로 파라미터가 없는 형태이다. 추상메소드 get() 를 가진다.이를 이용해 Lazy Evalutioan이 가능하다.

      package cc.kevinlee.modernjava.e05_supplier;
      
      import java.util.concurrent.TimeUnit;
      import java.util.function.Supplier;
      
      /**
       * @author Kevin Lee
       * @since 2015-08-01
       */
      public class SupplierExamples {
      
        /**
         * 계산 하는데 오래 걸리는 메소드를 시뮬레이션 해봤습니다. 항상 3초가 걸려요.
         *
         * @return 항상 "Kevin"만 리턴
         */
        private static String getVeryExpensiveValue() {
          try {
            TimeUnit.SECONDS.sleep(3);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          return "Kevin";
        }
      
        private static void callingExpensiveMethodWithoutSupplier() {
          System.out.println("SupplierExamples.callingExpensiveMethodWithoutSupplier()");
          final long start = System.currentTimeMillis();
          printIfValidIndex(0, getVeryExpensiveValue());
          printIfValidIndex(-1, getVeryExpensiveValue());
          printIfValidIndex(-2, getVeryExpensiveValue());
          System.out.println("It took " + ((System.currentTimeMillis() - start) / 1000) + " seconds.");
      
        }
      
        private static void printIfValidIndex(final int number, final String value) {
          if (number >= 0) {
            System.out.println("The value is " + value + ".");
          } else {
            System.out.println("Invalid");
          }
        }
      
        private static void callingExpensiveMethodWithSupplier() {
          System.out.println("SupplierExamples.callingExpensiveMethodWithSupplier()");
          final long start = System.currentTimeMillis();
          printIfValidIndex(0, () -> getVeryExpensiveValue());
          printIfValidIndex(-1, () -> getVeryExpensiveValue());
          printIfValidIndex(-2, () -> getVeryExpensiveValue());
          System.out.println("It took " + ((System.currentTimeMillis() - start) / 1000) + " seconds.");
        }
      
        private static void printIfValidIndex(final int number, final Supplier<String> valueSupplier) {
          if (number >= 0) {
            System.out.println("The value is " + valueSupplier.get() + ".");
          } else {
            System.out.println("Invalid");
          }
        }
      
        private static void supplierExamples() {
          System.out.println("SupplierExamples.supplierExamples()");
          final Supplier<String> helloSupplier = () -> "Hello ";
          System.out.println(helloSupplier.get() + "world");
        }
      
        public static void main(final String[] args) {
          supplierExamples();
      
          System.out.println("\nSupplier 사용하지 않고 시간 오래 걸리는 메소드 호출");
          callingExpensiveMethodWithoutSupplier();
      
          System.out.println("\nSupplier 사용해서 시간 오래 걸리는 메소드 호출");
          callingExpensiveMethodWithSupplier();
        }
      
      }

Reference

https://github.com/Kevin-Lee/modern-java-untold