2020-10-13 백기선님 더자바 강의정리

더자바

함수형 인터페이스와 람다

  • 인터페이스에 추상메서드가 1개만 있으면 함수형 인터페이스.

  • 2개가 있으면 안된다.

  • @FunctionalInterface
    public interface RunSomething {
    
      void doIt();
    
      static void printName() {
        System.out.println("jimmy");
      }
    
      default void printAge() {
        System.out.println("30");
      }
    }
    
    public class Run {
      public static void main(String[] args) {
        // 익명 내부 클래스
        RunSomething runSomething = new RunSomething() {
          @Override
          public void doIt() {
            System.out.println("구현체");
          }
        };
    
        // 람다표현식
        RunSomething runSomething = () -> System.out.println("구현체");
      }
    
      RunSomething runSomething = () -> {
        System.out.println("구현체");
        System.out.println("여러줄");
      };
      runSomething.doIt();
    }
    
    • 함수형 인터페이스를 잘 쓰기 위해서 @FuntionalInterface를 붙이면 좋다.
  • 함수형 인터페이스

    • 추상메서드를 1개만 가지고 있는 인터페이스
    • SAM(Single Abstract Method) 인터페이스
    • @FuntionalInterface 어노테이션을 갖고 있는 인터페이스
  • 람다 표현식

    • 함수형 인터페이스의 인스턴스를 만드는 방법으로 쓸 수 있다.
    • 코드를 줄일 수 있다.
    • 메서드의 매개변수, 리턴 타입, 변수로 만들어 사용할 수 있다.

자바에서 함수형 프로그래밍

  • 함수를 First Class Citizen으로 사용할 수 있다.
  • 순수함수 : 함수내부에서만 선언된 변수만 써야한다.
    • 사이드 이팩트를 만들 수 없다. => 함수밖에 있는 값 변경을 못한다.
    • 상태가 없다. => 함수 밖에 정의되는.
  • 고차함수(High-Order Function) : 함수가 함수를 매개변수로 받을 수 있고 함수를 리턴할 수 있다.
  • 불변성

함수형 메서드

  • Function<T, R> : 정의하면 apply로 적용
  • UnaryOperator<T> : 입력값의 타입과 결과값의 타입이 같을때
public class Run {
  public static void main(String[] args) {
    Function<Integer, Integer> plus10 = (i) -> i+10;
    Function<Integer, Integer> multiply2 = (i) -> i * 2;

    Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multiply2);
    System.out.println(multiply2AndPlus10.apply(2));

    Function<Integer, Integer> tmp = plus10.andThen(multiply2);
    System.out.println(tmp.apply(2));
  }
}
// result
14
24
  • compose가 입력값을 넣기전에 연산을 한번 한다. 고차함수의 성격

  • compose안에 있는 연산을 먼저하고 그다음 연산을 수행

  • andThen()은 앞에 연산을 먼저하고 안에 연산을 수행.

  • BiFunction<T, U, R> : 입력값을 2개 받는다.

  • Consum<T> : 입력만 받고 리턴은 void

  • Supplier<T> : 입력값이 없고 어떤 값을 받아올지 타입을 정한다

    • Supplier<Integer> get10 = () -> 10;
      
  • Predicate<T> : 인자값을 1개받아서 Boolean으로 리턴해준다.

    • public class Run {
        public static void main(String[] args) {
          Predicate<String> startWith = (str) -> str.startsWith("h");
          Predicate<Integer> isEven = (i) -> i % 2 == 0;
          System.out.println(startWith.test("hello"));
          System.out.println(isEven.test(10));
        }
      }
      

람다표현식

  • body가 1줄이면 {}생략 가능

  • 변수캡처

    • package me.jimmy.java8;
      
      import java.util.function.Consumer;
      import java.util.function.IntConsumer;
      
      public class Run {
        public static void main(String[] args) {
          Run run = new Run();
          run.tmp();
        }
      
        private void tmp() {
          int baseNumber = 10;
      
          // 로컬클래스
          class LocalClass {
            void printBaseNumber() {
              int baseNumber = 11; // 함수안에 있는 baseNumber가 tmp에 있는 baseNumber를 가린다.
              System.out.println("local class " + baseNumber); // 11
            }
          }
      
          // 익명클래스
          Consumer<Integer> integerConsumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer baseNumber) {
              System.out.println("익명 클래스 " + baseNumber); // parameter 이름으로 전달된 baseNumber를 쓴다.
            }
          };
      
          IntConsumer printInt = (i) -> {
            System.out.println(i + baseNumber);
          };
          printInt.accept(10);
        }
      
        // 에러난다.
        IntConsumer printInt = (baseNumber) -> {
          System.out.println(baseNumber);
        };
      }
      
      
    • 로컬클래스와 익명클래스에서 외부변수를 참조하는게 람다와는 다르다.

    • baseNumber가 사실상 final. 어디서도 변경하지 않는 경우. 이를 사실상 final. effective final. 이 경우 로컬클래스, 익명클래스, 람다에서 모두 다 baseNumber를 참조 가능하다.

    • 람다와 익명클래스,로컬클래스에서 스코프영역이 적용되는게 다르다. 람다는 스코프가 람다를 감싸고 있는 것과 같다. 따라서 쉐도잉이 일어나지 않는다.

    • 같은 스코프에서는 똑같은 이름의 변수를 정의할 수 없다.

메서드레퍼런스

  • ::가 붙으면 메서드 레퍼런스다.

  • public class GreetingApp {
      public static void main(String[] args) {
        Greeting greeting = new Greeting();
        UnaryOperator<String> hello = greeting::hello; // non-static
        UnaryOperator<String> hi = Greeting::hi; // static
    
        Supplier<Greeting> newGreet = Greeting::new;
        newGreet.get();
    
        Function<String, Greeting> conGreet = Greeting::new;
        conGreet.apply("jim");
    
      }
    }
    
    public class Greeting {
      private String name;
    
      public Greeting() {
      }
    
      public Greeting(String name) {
        this.name = name;
      }
    
      public String hello(String name) {
        return "hello " + name;
      }
    
      public static String hi(String name) {
        return "hi " + name;
      }
    }
    
    
  • 메서드 참조방법

    • 스태틱 메서드 참조 : 타입::스태틱 메서드
    • 특정 객체의 인스턴스 메서드 참조 => 객체레퍼런스::인스턴스메서드
    • 임의 객체의 인스턴스 메서드 참조 => 타입::인스턴스 메서드
    • 생성자 참조 => 참조::new
    • 메서드, 생성자의 매개변수로 람다의 입력값을 받고. 리턴값, 생성한 객체는 람다의 리턴값이다.
public class GreetingApp {
  public static void main(String[] args) {
    String [] names = {"jimmy", "a", "b"};

    Arrays.sort(names, new Comparator<String>() {
      @Override
      public int compare(String o1, String o2) {
        return 0;
      }
    });

    Arrays.sort(names, String::compareToIgnoreCase);

    Arrays.sort(names, (o1, o2) -> {
      return 0;
    });
  }
}

Written on October 13, 2020