[java8-in-action] Part I. 기초

자바 11이 나온 상태인데 8도 잘 모르는 상태에서는… 도저히 못버티겠다
공부하자

기초

  • 스트림 처리

    • 스트림: 한번에 한개씩 만들어지는 연속적인 데이터 항목들의 모임
    • 유닉스에서 pipe로 cat,tr, tail,sort처리시 병렬처리 가능
    • Java8부터 java.util.stream패키지에 스트림API 추가됨
    • 스트림 파이프라인을 입력해서 입력부분을 여러 코어에 쉽게 할당가능
    • 쓰레드라는 복잡한 작업 없이 공짜로 병렬성 가능
  • 코드를 메서드에 전달하기 (동작 파라미터화)

    • 자바 8 이전에는 메서드를 다른 메서드로 전달할 방법이 없었다
    • 동작 파라미터화 : behavior parameterization
  • 병렬성을 공짜로 얻을 수 있다. 그러면 무엇을 포기해야 하는가?

    • 스트림 메서드로 전달하는 코드의 동작 방식을 바꾸어야 한다
    • 처음엔 불편, 전달코드는 동시 실행시에도 안전하게 실행될 수 있어야 한다
      • shared mutable data에 접근 하면 안됨
      • stateless pure function
    • synchronized로 shared mutable data를 보호하는 규칙을 만들 수 있으나 일반적으로 synchronized는 시스템 성능에 악영향을 미침
    • 멀티코어에서는 코드가 순차적으로 실행되어야하므로 synchronized를 사용하게 되면 병렬이라는 목적이 무력화 되면서 훨씬 비싼 대가를 치룰 확율이 크다
  • 공부할 내용

    • 메소드 레퍼런스(method reference)
    • 익명 함수(anonymous function) Or 람다
    • 스트림
      • 기존 콜렉션 -> for-each 루프, 외부반복
      • 스트림 -> 내부에서 처리, 내부 반복
    • 디폴트 메소드
    • Optional

2. behavior parameterization code 전달

  • 동작 파라미터화
    • behavior parameterization
    • 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록
    • 이 블록은 나중에 프로그램에 의해 호출->실행이 나중으로 미루어짐
    • 예를들어 나중에 실행될 메서드의 인스로 이 코드 블록이 전달될 수 있음

2.1 변화하는 요구사항에 대응

  1. 농장 재고 목록 어플리케이션에서 녹색 사과만 필터링 하는 기능을 추가하여라
    녹색사과 필터링
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>(); //사과 누적 리스트
    for (Apple apple : inventory) {
    if("green".equals(apple.getColor())) {
    result.add(apple);
    }
    }
    return result;
    }
  2. 빨간 사과도 필터링 추가하여라
  • 한번은 그냥 메소드를 복사해서 새로 filterRedApples라는 메소드로 복사 붙이기
  • 다양한 색에 대한 추가적인 요구사항에 대응이 불가능
  • 해결책
    • 메서드에 파라미터를 추가하여 요구사항에 좀더 유연하게 대응
      색을 파라미터화
      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
      32
      33
      34
      35
      36
      37
      38
      39
      public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
      List<Apple> result = new ArrayList<>(); //사과 누적 리스트
      for (Apple apple : inventory) {
      if(apple.getColor().equals(color)) {
      result.add(apple);
      }
      }
      return result;
      }
      ```
      3. 가벼운 사과와 무거운 사과 필터링 추가
      * 요구사항에 이제 다양한 색과 함께 다양한 무게의 기준도 얼마든지 빠뀔 수 있다
      * 위처럼 색대신 무게를 받는 필터 메소드?
      * 목록검색해서 각 필터링 거는 코드가 중복된다
      * 모든 속성을 메서드 인수로 추가?
      * 덩치가 커지고 요구사항 변경시 유연하게 대응이 불가능

      ## 2.2 동작 파라미터화
      * 선택조건
      * 사과의 어떤 속성에 기초해서 boolean값 리턴 -> 프레디케이트(predicate)
      ```java 사과를 선택하는 다양한 전략
      //선택 조건을 결정하는 인터페이스 정의
      public interface ApplePredicate {
      boolean test (Apple apple)
      }

      //다양한 선택조건을 대표하는 여러 버전의 ApplePredicate
      public class AppleHeavyWeightPredicate implements ApplePredicate {
      @Override
      public boolean test(Apple apple) {
      return apple.getWeight() > 150;
      }
      }
      public class AppleGreenColorPredicate implements ApplePredicate {
      @Override
      public boolean test(Apple apple) {
      return "green".equals(apple.getColor());
      }
      }
  • ApplePredicate는 사과 선택 전략을 캡슐화
  • strategy design pattern
    • 전략 디자인 패턴
    • 각 알고리즘(전략)을 캡슐화하는 정의해두고 런타임에 전략을 선택하는 기법
  • filterApple메소드가 ApplePredicate 객체를 매개변수로 받도록 고친다
    • filterApple 메서드 내부에서 컬렉션 반복로직과 컬렉션 각 요소에 동작할 로직(여기서는 predicate)를 분리할 수 있다는 점에서 소프트웨어 엔지니어링 적으로 큰 이득
ApplePredicate를 filiter메소드에 적용
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
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p ) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if(p.test(apple)) { //predicate 객체로 검사 조건을 캡슐화
result.add(apple);
}
}
return result;
}
```
* 결과
* 처음 코드보다 유연한 코드, 가독성 좋고 사용하기도 쉬움
* 현재 메서드는 객체만 인수로 받으므로 test 메소드를 ApplePredicate로 래핑 전달
* test메서드 구현 객체를 이용해서 boolean 표현식 전달 -> 코드 전달과 마찬가지

## 2.3 복잡한 과정 간소화
* 현재 장점을 가졌지만 단점도 있음
* 요구사항을 반영한 각각 클래스를 구현해서 인스턴스화 하는 과정이 필요하다

### 2.3.1 익명 클래스
* annymous class
* java의 local class와 비슷한 개념
* 이름없는 클래스, 클래스 선언과 인스턴스화가 동시에 가능
* 즉석에서 필요한 구현 가능
* GUI 어플리케이션에서 이벤트 핸들러 객체를 구현할때 종종 사용된다
```java 익명 클래스 사용해서 구현
public static List<Apple> filterApples(List<Apple> inventory, new ApplePredicate(){
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
  • 결과
    • 익명클래스로 구현해도 많은 코드 공간을 차지 하고 있음
    • 코드의 가독성을 떨어뜨리고 추측을 힘들게 한다
      익명 클래스 문제. 다음 코드를 실행한 결과는 ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class MeaningOfThis {
      public final int value =4;
      public void doIt() {
      int value = 6;
      Runnable r = new Runnable() {
      public final int value =5;
      public void run() {
      int value = 10;
      System.out.println(this.value);
      }
      };
      }
      public static void main(String... args) {
      MeaningOfThis m = new MeaningOfThis();
      m.doIt();
      }
      }
    • 코드의 장황함(verbosity)는 나쁜 특성이다
    • 코드조각(예를들어 boolan표현식)전달에서 결국 객체를 만들고 새로운 동작을 정의하는 메소드를 만들어야 하는 점에서 같다

2.3.3 람다 표현식

람다 표현식으로 간단하게 구현
1
List<Apple> result = filterApples(inventory,(Apple apple) -> "red".equals(apple.getColor()));

2.3.4 리스트 형식으로 추상화

  • 현재 filterApples는 Apple과 관련 동작만 수행
  • Apple외의 객체에서도 필터링이 작동하도록 리스트 형식을 추상화 가능
    Apple외의 객체에서도 필터링 작동가능하게 제네릭으로
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface Predicate<T> {
    boolean test(T t);
    }
    public static <T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList<>();
    for (T t: list){
    if(p.test(t)) {
    result.add(t);
    }
    }
    }

2.4

  • 동작 파라미터화는 변화하나는 요구사항에 쉽게 적응할 수 있는 유용한 패턴
  • 동작을 한조각의 코드로 캡슐화 한다음 메소드에 전달해서 메소드 동작을 파라미터화

2.4.1 Comparator로 정렬하기

  • 자바 8의 List에는 sort메소드가 포함되어 있다. Collections.sort도 존재한다
Comparator로 정렬하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// java.util.comparator의 인터페이스
public interface Comparator<T> {
public int compare(T o1, T o2);
}

//익명 클래스로 무게 적은 순으로 사과 정렬
inventory.sort(new Comparator<Apple>(){
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});

//람타 표현식
inventory.sort(
(Apple a1, Appl2 a2)->a1.getWeight().compareTo(a2.getWeight()));

2.4.2 Runnable로 코드 블록 실행

  • 자신만의 코드 블록을 수행한다는 점에서 스레드 동작은 lightweight process와 비슷
  • 각각의 스레드는 각기 다른 코드 실행 가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //java.lang.Runnable 인터페이스 구조
    public interface Runnable {
    public void run();
    }

    //Runnable을 이용한 스레드 동작 예
    Thread t = new Thread(new Runnable(){
    public void run() {
    System.out.println("hello");
    }
    });

    //람다 표현식
    Thread t = new Thread(()->System.out.println("hello"));

3. lambda

3.1 람타의 특징

  • 익명

    • 보통의 메소드와 달리 이름이 없는 익명이다
  • 함수

    • 메서드처럼 특정 클래스 종속되지 않아서 함수라고 부른다
    • 하지만 메소드처럼 파라미터 리스트, 바디, 리턴값, 예외리스트들을 포함한다
  • 전달

    • 람타 표현식을 메소드 인수로 전달하거나 변수로 저장 가능
  • 간결성

    • 익명클래스처럼 자질 구레한 코드 구현이 필요 없다.
  • 구조

    • (parameters) -> expression
    • (parameters) -> {statements;}

3.2 람다 사용처

  • 함수형 인터페이스에서 람타 표현식을 사용할 수 있다

3.2.1 함수형 인터페이스

  • 위에서 봤던 Predicate가 함수형 인터페이스
  • 정확히 하나의 추상 메서드를 지정하는 인터페이스
  • 자바 API의 Comparator, Runnable등
  • 많은 디폴트 메서드가 있더라도 추상 메소드가 오직 하나라면 함수형 인터페이스
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //r1 : 람다 사용
    Runnable r1 = ()->System.out.println("hello1");

    //r2: 익명클래스
    Runnable r2 = new Runnable(){
    public void run(){
    System.out.println("hello2");
    }
    };

    public static void Process(Runnable r) {
    r.run();
    }

    process(r1); //hello1 출력
    process(r2); //hello2 출력
    process(()->System.out.println("hello3")); //hello3출력

3.2.2 Function descriptor

  • 함수 디스크립터

    • 함수형 인터페이스의 추상 메서드 시그니처는 람다 표현식의 시그니처를 가르킨다
    • 람다 표현식의 시그니처를 서술하는 메소드
    • 예를들어 Runnable 인터페이스의 유일한 추상 메서드 run은 인수와 반환값이 없으므로 Runable 인터페이스는 인수와 반환값이 없는 시그니처로 생각할 수 있다
  • 왜 함수형 인터페이스를 인수로 받는 메서드에만 람타 표현식을 사용할 수 있는가?

    • 더 복잡하게 하지 않게 하기 위해서
    • 대부분의 자바 프로그래머가 하나의 추상 메서드를 가지는 인터페이스(예를 들어 이벤트 처리 인터페이스)에 이미 익숙하다
  • @FunctionalInterface

    • Runnable등의 정의에 보면 발견 할 수 있음
    • 함수형 인터페이스를 가르키는 어노테이션
    • @FunctionalInterface 밑의 인터페이스가 함수형 인터페이스가 아니면 컴파일러가 에러를 발생 시킴(예를들어 추상 메서드가 한개 이상)

3.3 람다 활용 : execute around pattern

  • 자원처리 (DB, File ,Resource)의 순환패턴(recurrent pattern)은 자원을 열고 처리후 닫는다. setup과 cleanup과정은 거의 비슷하다.
  • try-with-resource 구문
    • 자바7부터 추가된 구문
    • AutoCloseable 인터페이스가 추가 되어 있음
      AutoCloseable.java
      1
      2
      3
      4
      5
      6
      7
      /**
      * @author Josh Bloch //이펙티브 자바 저자?
      * @since 1.7
      */
      public interface AutoCloseable {
      void close() throws Exception;
      }
    • try절에 ()가 들어갈 수 있게 문법이 추가
    • try(){}형태로 가능하며 ()안에 올 수 있는 것은 AutoCloseable 구현체만 가능
    • 코드량을 획기적으로 감소
중복코드가 작업을 감싸게 된다
1
2
3
4
5
public static String processFile() throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
}
}

3.3.1 동작파라미터화를 기억하라

  • 한번에 두줄을 읽거나 가장 자주 사용되는 단어 반환하려면
    1
    String result = processFile((BufferedReader br)->br.readLine()+br.readLine());

3.3.2 함수형 인터페이스를 만들어서 동작 전달

1
2
3
4
5
6
7
8
9
//함수형 인터페이스 정의
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
//정의한 인터페이스를 processFile메소드의 인수로 전달할 수 있다
public static String processFile(BufferedReaderProcessor p) throws IOException{
...
}

3.3.3 동작실행

이제 BufferedReaderProcessor에 정의된 process메서드의 시그니처(BufferedReader->String)과 동일한 람다를 전달할 수 있다

함수형 인터페이스 실행
1
2
3
4
5
public static String processFile(BufferedReaderProcessor p) throws IOException{
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br); //BufferedReader 객체 처리
}
}
  • 전달된 코드는 함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식으로 처리

3.3.4 실제 람다 실행

한줄 처리와 두줄처리 람다
1
2
String oneLine = processFile((BufferedReader br)->br.readLine());
String twoLine = processFile((BufferedReader br)->br.readLine()+br.readLine());

3.4 함수형 인터페이스 사용

  • 함수형 인터페이스는 오직 하나의 추상 메서드
  • 함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사
  • 함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터라고 한다
  • 다양한 람다 표현식의 사용을 위해 보통의 함수 디스크립터를 기술하는 함수형 인터페이스 집단이 필요하다
  • 이미 자바API는 Comparable, Runnable, Callable등의 다양한 함수형 인터페이스 있다

3.4.1 Predicate

  • java.util.function.Predicate 인터페이스,
  • T제네릭을 인수로 받아 boolean을 반환하는 test라는 추상 메서드 정의
  • T 형식의 객체를 사용하는 boolean 표현식이 필요한 상황에 사용할 인터페이스

3.4.2 Consumer

  • java.util.function.Consumer 인터페이스
  • 제네릭 T객체를 받아 void를 반환하는 accept 추상 메서드를 정의
  • T형식을 인수로 받아 어떤 동작을 수행하고 싶을 때 이 인터페이스를 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @FunctionalInterface
    public interface Consumer<T> {
    void accept(T t);
    }

    public static <T> void forEach(List<T> list, Consumer<T> c){
    for(T t: list) {
    c.accept(t);
    }
    }

    forEach(
    Arrays.asList(1,2,3,4,5),
    (Integer i)-> System.out.println(i);
    );

3.4.3 Function

  • java.util.function.Function<T,R> 인터페이스
  • 제네릭 T를 인수로 받아서 제네릭 R을 반환하는 apply 추상 메서드 정의
  • 입력을 출력으로 매핑하는 람다를 정의할 때 이 인터페이스 활용
  • 예를들어 사과 무게를 추출하거나 문자열을 길이와 매핑
  • String 리스트를 인수로 받아 String 길이를 포함하는 Integer 리스트로 변환하는 map메서드를 정의하여라
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @FunctionalInterface
    public interface Function<T,R> {
    R apply(T t);
    }

    public static <T,R> List<R> map(List<T> list, Function<T,R> f) {
    List<R> result = new ArryList<>();
    for(T t : list){
    result.add(f.apply(t));
    }
    return result;
    }

    //람다의 apply 메서드 구현
    List<Integer> r= map(
    Arrays.asList("lambdas", "in", "action"),
    (String s)->s.length()
    );

기본형 특화

  • 자바의 모든 형식은 참조형 아니면 기본형에 해당한다

  • 제네릭파라미터(Consumer의T)에는 참조형만 사용할 수 있다

    • C#같은 언어에는 이런 제약이 없다
    • 스칼라 같은 언어에는 참조형만 존재
  • 변환 필요

    • boxing : 기본형 -> 참조형
      • 박싱한 값은 기본형을 감싸는 wrapper이며 힙에 저장됨
      • 메모리를 더 소비하며 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요
    • unboxing : 참조형 -> 기본형
    • Autoboxing: 박싱과 언박싱이 자동으로 이루어짐
  • 특화된 인터페이스

    • 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록
      1
      2
      3
      4
      5
      IntPredicate evenNumbers = (int i) -> i % 2 == 0;
      evenNumbers.test(1000); //true : boxing 없음

      Predicate oddNumbers = (integer i) -> i % 2 == 1;
      oddNumbers.test(1000); //false : boxing 처리
    • DoublePredicate, IntConsumer, LongBinaryOperator등의 형식명이 붙는다

예외처리

  • 함수형 인터페이스는 확인된 예외를 던지는 동작을 허용하지 않는다

  • 예외를 던지는 람다 표현식을 만드는 방법

    1. 확인된 예외를 선언하는 함수형 인터페이스를 직접 정의한다
      IOException을 명시적으로 선언하는 함수형 인터페이스
      1
      2
      3
      4
      5
      6
      @FunctionalInterface
      public interface BufferedReaderProcessor {
      String process(BufferedReader b) throws IOException;
      }

      BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();
    2. 람다를 try-catch로 싸서 명시적인 예외를 잡는다
      명시적으로 확인된 예외를 잡는다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      Function<BufferedReader, String> f =
      (BufferedReader b) -> {
      try{
      return b.readLine();
      } catch(IOException e) {
      throw new RuntimeException(e);
      }
      };
      ```

      ## 3.5 형식검사, 형식 추론, 제약
      * 깊이 있는 내용
      * 이해 안되면 일단 기록하고 나중에 살펴보자
      * 람다로 함수형 인터페이스의 인스턴스를 만들 수 있다? 맞다
      * 람다 표현식 자체엔 람다가 어떤 함수형 인터페이스를 구현했는지 정보가 없다
      * 따라서 람다 표현식을 더 제대로 이해하려ㅑ면 람다의 실제 형식을 파악해야 한다

      ### 3.5.1 형식검사
      * 람다가 사용되는 context를 사용해서 람다의 type을 추론할 수 있다
      * context: 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수등
      * 어떤 context에서 기대되는 람다 표현식의 형식을 대상 형식(target type)이라고 한다
      ```java 람다 표현식의 예제
      List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight()>150);
  • 위 코드의 형식 확인 과정

    1. filter메서드의 선언을 확인
    2. filter 메서드는 2번째 파라미터로 Predicate target type을 기대
    3. Predicate은 test라는 한 개의 추상 메서드를 정의한 함수형 인터페이스
    4. test메서드는 Apple을 전달받아 boolean을 반환하는 함수 디스크립터를 묘사
    5. filter메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다
  • 위의 람다 표현식 예제는 Apple을 인수로 받고 boolean을 리턴하므로 유효한 코드

  • 람다 표현식이 예외를 던질 수 있다면 추상 메서드도 같은 예외를 던지도록 throws로 선언해야 한다

3.5.2 같은람다, 다른 함수형 인터페이스

  • target type이라는 특징 때문에 같은 람다 표현식이라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다
    둘다 유효한 코드
    1
    2
    Callable<Integer> c = () -> 42;
    PrivilegedAction<Integer> p = () -> 42;
  • Callable, PrivilegedAction 둘다 인수 없이 제네릭 T를 반환하는 함수를 정의한다
  • 따라서 위의 두 코드는 둘다 유효한 코드다
  • 첫코드의 target type은 Callable, 다음코드의 target type은 PrivilegedAction로 다른 대상 형식(target type)이다
    하나의 람다 표현식을 다양한 함수형 인터페이스에 사용할 수 있다
    1
    2
    3
    4
    5
    6
    Comparator<Apple> c1 =
    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    ToIntBiFunction<Apple, Apple> c1 =
    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    BiFunction<Apple, Apple, Integer> c1 =
    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

다이아몬드 연산자

자바7 이전의 제네릭 선언 방법
1
2
3
4
//Case 1
List<Integer> list = new LinkedList();
//Case 2
List<Integer> list2 = new LinkedList<Integer>();
  • case1의 경우 Integer를 저장하기로 선언하였으나 LinkedList로 초기화 할때 별다른 조건을(generic) 걸지 않아서 LinkedList에 String,Integer등 아무 타입을 넣어도 컴파일러가 컴파일 에러를 내지 않아 에러 수정이 힘들다. 추후에 런타임 에러가 발생하면 때늦은 수정을 하게 된다. (자바7부터는 case1에 컴파일 경고가 뜨게 된다)
  • case2 처럼 명확하게 선언, 초기화 해주는게 좋은데 DRY원칙의 중복 문제가 있다
  • 다이아몬드 연산자를 사용하면 컨텍스트에 따른 제네릭 형식을 유추해서 자동 설정
    다이아몬드 연산자 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // <>안에 Integer로 초기화 될것을 추론할 수 있다
    List<Integer> list3 = new LinkedList<>();

    //다이아몬드 연산자는 클래스의 인스턴스화를 단순화한다
    //예를들어
    List<Map<Integer,Set<String>>> p = new ArrayList<Map<Integer,Set<String>>>();

    //위 문장을 반복유형을 제거하여 다음과 같이 사용된다
    List<Map<Integer,Set<String>>> p = new ArrayList<>();

특별한 void 호환 규칙

  • 람다 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다
  • 물론 파라미터 리스트도 호환되어야 한다
    1
    2
    3
    4
    //Predicate는 boolean 리턴값을 갖는다
    Predicate<String> p = s -> list.add(s);
    //Consumer는 void 리턴값을 갖는다
    Consumer<String> p = s -> list.add(s);
  • 위 예제에서 List의 add메서드는 boolean 값을 갖는다
  • 이는 Consumer가 기대하는 void리턴값과 다르지만 유효한 코드다

3.5.3 형식 추론

  • 자바 컴파일러는 람다 표현식의 컨텍스트(target type)을 이용해 람다 표현식과 관련된 함수형 인터페이스를 추론한다
  • 즉 target type을 통해 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론할 수 있다
  • 결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있다
    파라미터 a에 형식을 명시적으로 지정하지 않음
    1
    List<Apple> greenApples = filter(inventory, a-> "green".equals(a.getColor()));
  • 여러 파라미터를 포함하는 람다 표현식에서 코드 가독성 향상이 더 두드러진다
    1
    2
    3
    4
    5
    6
    //형식을 추론하지 않음
    Comparator<Apple> c1 =
    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
    //형식을 추론
    Comparator<Apple> c2 =
    (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
  • 상황에 따라 명시적으로 형식을 포함하는 것이 좋을 때도 있고
    형식을 배제하는 것이 가독성을 향상시킬 때도 있다. 정해진 규칙은 없다

3.5.4 지역변수 사용

  • 지금까지 람다 표현식은 인수를 자신의 바디 안에서만 사용
  • 익명함수처럼 자유 변수(free variable)을 활용할 수 있다
  • free variable: 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수
  • 이같은 동작을 람다 캡처링(capturing lambda)이라고 부른다
    portNumber 변수를 캡처하는 람다 예제
    1
    2
    int portNumber=8080;
    Runnable r = ()-> System.out.println(portNumber);
  • 람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처(바디에서 참조)할 수 있다
  • 그러기 위해선 지역변수는 명시적으로 final 선언이 되어있거나 실질적으로 final 변수처럼 사용되어야 한다
  • 즉, 람다 표현식은 한번만 할당할 수 있는 지역 변수를 캡처할 수 있다
  • 인스턴스 변수 캡처는 final 지역변수 this를 캡처하는 것과 마찬가지다
    컴파일할 수 없는 코드
    1
    2
    3
    int portNumber=8080;
    Runnable r = ()-> System.out.println(portNumber);
    portNumber=80; //값을 2번 할당하므로 컴파일할 수 없다

지역변수 제약

  • 내부적으로 지역변수와 인스턴스 변수는 태생부터 다르다
  • 인스턴스 변수는 힙에 저장되고 지역변수는 스택에 위치한다
  • 람다에서 지역변수에 바로 접근할수 있다고 가정하고 람다가 스레드에서 실행된다면
    변수를 할당한 스레드가 사라져서 변수할당이 해제되었는데도 람다가 실행하는 스레드에서는 해당 변수에 접근하려는 경우가 생길 수 있다
  • 따라서 자바구현에서는 원래 변수에 접근을 허용하는 것이 아닌 자유 지역 변수의 복사본을 제공한다
  • 따라서 복사본의 값이 바뀌지 않아야 하기에 한번만 값을 할당해야한다는 제약이 붙은 것이다
  • 또한 지역변수의 제약 덕분에 외부 변수를 변화시키는 일반적인 명령형 프로그래밍 패턴(병렬화를 방해하는 요소)에 제동을 걸 수 있다

람다와 클로저

  • 람다는 클로저(Closure)에 부합되는가?
  • 클로저: 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스
  • 클로저는 다른 함수의 인자로 전달될 수 있으며 클로저 외부에 정의된 변수의 값에 접근해서 값을 바꿀 수 있다
  • 자바8의 람다와 익명클래스는 클로저와 비슷한 동작을 수행한다
  • 둘다 모두 메서드의 인수로 전달 될 수 있고 자신의 외부 영역의 변수에 접근 할 수 있다
  • 다만 람다와 익명클래스는 람다가 정의된 메서드의 지역변수의 값은 바꿀 수 없다
  • 람다가 정의된 메서드의 지역 변수값은 final이어야 한다
  • 위에서 말했다시피 지역변수는 스택에 존재하고 자신을 정의한 스레드와 생존을 같이 해야하므로 final이어야 한다
  • 인스턴스 변수는 스레드가 공유하는 힙에 존재하므로 특별한 제약이 없다

3.6 메서드 레퍼런스(method reference)

  • 간단하세 생각하면 특정 람다 표현식을 축약한 것?
  • 기존 메서드 정의를 재활용해서 람다처럼 전달이 가능하다
  • 때로는 람다 표현식보다 메서드 레퍼런스가 가독성이 좋고 자연스럽다
    람다와 메서드 레퍼런스의 비교
    1
    2
    3
    4
    //lambda
    inventory.sort((Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight()));
    //method reference와 java.util.Comparator.comarping을 활용
    inventory.sort(comparing(Apple::getWeight));

3.6.1 Summery

  • 메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 축약형이라고 생각할 수 있다
  • 람다가 ‘이 메서드를 직접 호출해’라고 명령한다면 메서드를 어떻게 호출할지 설명을 참조하는 것보다는 메서드명을 직접 참조하는 것이 편리하다
  • 실제 메서드 레퍼런스를 이용해서 기존 메서드 구현으로 람다 표현식이 가능하다
  • 이때 명시적으로 메서드 이름을 참조함으로써 가독성을 높일 수 있다
  • 참조방법: 메스명 앞에 구분자(::)를 붙이는 방식
  • 실제 호출이 아니므로 괄호는 필요없다

|람다|메서드 레퍼런스 단축표현|
|-::|-::|
|(Apple a) -> a.getWeight()|Apple::getWeight|
|() -> Thread.currentThread().dumpStack()|Thread.currentThread()::dumpStack|
|(str, i) -> str.subString(i)| String::subString|
|(String s) -> System.out.println(s)|System.out::println|

  • 메서드 레퍼런스를 새기능이 아닌 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법으로 간주할 수 있다

메서드 레퍼런스를 만드는 방법

  • 메서드 레퍼런스 유형

    1. 정적 메서드 레퍼런스
    • ex)Integer의 ParseInt()는 Integer::parseInt로 표현할 수 있다
    1. 다양한 형식의 인스턴스 메서드 레퍼런스
    • ex)String의 length()는 String::length로 표현할 수 있다
    1. 기존 객체의 인스턴스 메서드 레퍼런스
    • ex) Transaction 객체를 할당받은 localTransaction 지역변수가 있고, Transaction 객체에 getValue메서드가 있다면 localTransaction::getValue로 표현할 수 있다
  • 생성자, 배열 생성자, super호출에 사용할 수 있는 특별한 형식의 메서드 레퍼런스

    • 예)List에 포함된 문자열을 대소문자 구분 없이 정렬
    • List의 sort메서드는 인수로 Comparator를 기대한다
    • Comparator는 (T,T) -> int라는 함수 디스크립터를 가진다
1
2
3
4
5
6
List<String> str = Arrays.asList("a","b","A","B");

str.sort((s1, s2) ->s1.compareToIgnoreCase(s2));
//위 람다표현식의 시그니처는 Comparator의 시그니처와 호환된다
//그러면 위의 설명한 기법을 쓰면 메서드 레퍼런스를 통해 다음과 같이 가능하다
str.sort(String::compareToIgnoreCase);
  • 컴파일러는 람다 표현식 검사방식과 비슷한 과정으로 메서드 레퍼런스가 주어진 함수형 인터페이스와 호환되는지 확인한다 -> 메서드 레퍼런스는 콘텍스트 형식과 일치해야한다

3.6.2 생성자 레퍼런스

  • ClassName::new 처럼 클래스명과 new키워드를 이용해서 기존 생성자의 레퍼런스를 만들 수 있다

  • 정적 메서드의 레퍼런스를 만드는 것과 비슷하다

    1
    2
    3
    4
    5
    6
    7
    //Sypplier의 () -> Apple같은 시그니어의 생성자가 있다고 가정
    Supplier<Apple> c1 = Apple::new;
    Apple a1 = c1.get();

    //Apple(Integer weight) 생성자 시그니처는 Function인터페이스 시그니처와 같다
    Function<Integer, Apple> c2 = Apple::new;
    Appl2 a2 = c2.get(110);
  • 무게를 List로 전달해서 그 무게를 가진 사과들로 이루어진 리스트를 뽑아낸다면?

    다양한 무게를 포함하는 사과 리스트가 만들어진다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    List<Integer> weights = Arrays.asList(7,3,4,10);
    List<Apple> apples = map(weights, Apple::new); //map메서드에 생성자 레퍼런스 전달

    public static List<Apple> map(List<Integer> list, Function<Integer, Apple> f){
    List<Apple> result = new ArrayList<>();
    for(Integer e : list) {
    result.add(f.apply(e));
    }
    return result;
    }
  • Apple(String color, Integer weight)처럼 두 인수를 갖는 생성자는 BiFunction 인터페이스와 같은 시그니처를 가지므로 다음처럼 할 수 있다

    1
    2
    3
    4
    5
    6
    7
    8
    //메서드 레퍼런스
    BiFunction<String, Integer, Apple> c3 = Apple::new;
    Apple a3 = c3.apply("green", 110);

    //람다 표현식
    BiFunction<String, Integer, Apple> c4 =
    (color, weight) -> new Apple(color, weight);
    Apple a4 = c4.apply("green", 110);
  • 인스턴스화 하지 않고도 생성자에 접근할 수 있는 기능을 다양항 상황에 응요할 수 있다

  • 예를들어 Map으로 생성자와 문자열값을 관련시키고 String, Integer가 주어질때 다양한 무게를 갖는 다양한 과일을 만드는 giveMeFruit라는 메서드를 만들 수 있다

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
    static {
    map.put("apple", Apple::new);
    map.put("orange", Orange::new);
    //등등 추가
    }

    public staitc Fruit giveMeFruit(String fruit, Integer weight) {
    return map.get(fruit.toLowerCase()) //map에서 Function<Integer,Fruit> 얻는다
    .apply(weight); //Function의 apply에 무게 파라미터 제공
    }

3.7 람다와 메서드 레퍼런스를 활용해보자

  • 지금까지 했던 사과리스트 정렬문제를 다 세련되고 더 간결하게 활용해보자

3.7.1 코드 전달

  • 자바8의 List API에 sort 메서드를 제공하므로 정렬을 구현할 필요는 없다

  • Comparable vs Comparator

    • Comparable - 기본 정렬기준을 구현하는데 사용
    • Comparator - 기본 정렬 기준외의 다른 기준으로 정렬하고자 할때
    • 자바의 String, Integer, Date, File등 같은 타입의 인스턴스를 비교할 수 있는 클래스들은 전부 Comparable이 구현되어 있다
    • Comparable 인터페이스는 compareTo 추상메서드를 가진다(함수형 인터페이스 아님)
    • 보통 작은값->큰값, 오름차순 형태로 구현되어있다 -> 기본정렬 기준
    • Arrays.sort(colors), Arrays.sort(weights)하면 이 기본정렬에 맞춰 정렬된다
    • 배열이 아닌 Arraylist라면? Arrays.sort()대신 Collections.sort() 사용
    • 역시 저 기본 정렬기준에 맞춰서 정렬이 된다
    • 어떤 VO의 List에 sort를 하면? 오류발생! 기준이 없기 떄문
    • VO 가 Comparable을 implements하고 compareTo()를 구현하면 해결 가능
  • 어떻게 sort메서드에 정렬 전략을 전달할 것인가?

    sort 메서드 정의를 보면 다음과 같은 시그니처를 갖는다
    1
    void sort(Comparator<? super E> c)
  • sort메서드는 Comparator 객체를 인수로 받아서 두 사과를 비교하게 된다

  • 객체 안에 동작을 포함시키는 방식으로 다양한 전략을 전달 할 수 있다

  • 이제 “이 sort동작은 파라미터화 되었다”라고 말할 수 있다
    즉 sort에 전달되는 전략에 따라 sort의 동작이 바뀌게 된다

    Apple 비교 전략
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class AppleComparator implements Comparator<Apple> {
    public int compare(Apple a1, Apple a2) {
    return a1.getWeight().compareTo(a2.getWeight());
    }
    }
    ```

    ### 3.7.2 익명 클래스 사용
    * 한번만 사용할 Comparator이기에 위 코드보다는 익명 클래스를 이용하는 것이 좋다
    ```java 익명 클래스로 바꿔서 사용하기
    inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2) {
    return a1.getWeight().compareTo(a2.getWeight());
    }
    });

3.7.3 람다 표현식 사용

  • 함수형 인터페이스를 기대하는 어느곳이든 람다 표현식을 사용할 수 있다

  • 함수형 인터페이스는 오직 하나의 추상 메서드를 정의하는 인터페이스다

  • 추상 메서드의 시그니처(함수 디스크립터)는 람다 표현식의 시그니처를 정의한다

  • Comparator의 함수 디스크립터는 (T,T) -> int다

  • Apple을 사용하기에 더 정확히는 (Apple, Apple) -> int로 표현할 수 있다

    람다 표현식 사용
    1
    2
    3
    inventory.sort((Apple a1, Apple a2) ->
    a1.getWeight().compareTo(a2.getWeight())
    );
  • 람다 표현식은 컨텍스트를 활용해서 람다의 파라미터 형식을 추론 할 수 있으므로

    파라미터 추론을 이용한 코드 간소화
    1
    inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
  • 가독성 더 향상?

    • Comparator는 Comparable 키를 추출해서 Comparator 객체로 만드는 Function함수를 인수로 받는 정적 메서드 comparing을 사용한다
      Comparator의 comparing함수
      1
      2
      3
      4
      5
      6
      7
      public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
      Function<? super T, ? extends U> keyExtractor)
      {
      Objects.requireNonNull(keyExtractor);
      return (Comparator<T> & Serializable)
      (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
      }
  • 이 comparing 메서드를 다음처럼 사용할 수 있다

    위의 코드와 비교해서 잘 살펴보자. 정말 멋지다
    1
    2
    //사과를 비교하는데 사용할 키를 어떻게 추출할 것인지 지정하는 한개의 인수만 포함
    Comparator<apple> c = Comparator.comparing((Apple a) -> a.getWeight());
  • 이제 코드를 다음과 같이 간소화 할 수 있다

    1
    2
    import static java.util.Comparator.comparing;
    inventory.sort(comparing((a)->a.getWeight()));

3.7.4 메서드 레퍼런스 사용

  • 메서드 레퍼런스를 이용하면 코드를 조금 더 간소화 가능하다
    완성된 최적의 코드
    1
    2
    import static java.util.Comparator.comparing;
    inventory.sort(comparing(Apple::getWeight));
  • 코드만 짧아 진것이 아니라 코드 자체로 “Apple을 weight별로 비교해서 ionventory에 sort하라”는 의미를 전달 할 수 있다

3.8 람다 표현식을 조합할 수 있는 유용한 메서드

  • 자바 8 API의 몇몇 함수형 인터페이스, 예를들어 Comparator, Function, Predicate같은 함수형 인터페이스는 람다 표현식을 조합할 수있는 유틸리티 메서드를 제공한다
  • 간단히 말해 간단한 여러 람다 표현식을 조합해서 복잡한 람다 표현식을 만드는게 가능
  • 예를 들어 두 predicate를 조합해서 or 연산을 수행하는 큰 predicate만들기
  • 함수형 인터페이스에서 어떻게 메서드를 추가로 제공하는가? Default메서드

3.8.1 Comparator 조합

  • 위에서 봤듯이 Comparator.comparing을 이용해서 비교에 사용할 키를 추출하는 Function기반의 Comparator를 반환할 수 있다.
    1
    Comparator<apple> c = Comparator.comparing(Apple::getWeight);
역정렬
  • getWeight의 Integer의 Comparable 기본 규칙에 따라 오름차순으로 정렬된다
  • 내림차순으로 하려면 ? 순서를 바꾸는 reverse라는 디폴트 메서드를 제공한다
    reverse를 사용하여 새로운 Comparator 인스턴스 없이 역정렬 가능
    1
    inventory.sort(comparing(Apple::getWeight).reversed()); //내림차순 정렬

Comparator 연결

  • 무게가 같은 사과가 있을때 어떤 사과를 먼저 나열할 것인가?
  • 결과를 더 다듬을 수 있는 두번쨰 Comparator를 만들 수 있다
  • 무게가 같으면 원산지별로 정렬한다던지 가능
  • thenComparing메서드로 두번쨰 비교자를 만들 수 있다
  • thenComparing: comparing메서드처럼 함수를 인수로 받으며 첫번쨰 비교자를 이용해서 두 객체가 같다고 판단되면 두번쨰 비교자에 객체를 전달한다
    무게가 같으면 국가별로 정렬
    1
    2
    3
    inventory.sort(comparing(Apple::getWeight)
    .reversed()
    .thenComparing(Apple::getCountry));

3.8.2 Predicate 조합

  • Predicate는 복잡한 프레디케이트를 만들 수 있는 negate, and, or 메서드를 제공
    Predicate combine example
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //특적 프레디게이트 반전 예 : 빨간색이 아닌 사과
    //기존 프레디케이트 객체 redApple의 결과를 반전시킨 객체를 만든다
    Predicate<Apple> notRedApple = redApple.negate();

    //and를 사용해서 조합 가능
    //빨간색이면서 무거운 사과
    Predicate<Apple> redAndHeavyApple =
    redApple.and(a -> a.getweight() > 150);

    //or를 이용해서 빨간색이면서 무거운사과 혹은 그냥녹색사과
    Predicate<Apple> redAndHeavyAppleOrGreen =
    redApple.and(a -> a.getweight() > 150)
    .or(a -> "green".equals(a.getColor()));
  • 단순 람다를 조합해서 더 복잡한 람다 표현식을 이처럼 만들 수 있다
  • 이 and, or는 왼쪽에서 오른쪽 순이다. a.or(b).and(c) = (a || b) && c

3.8.3 Function 조합

  • Function인터페이스는 Function인스턴스를 리턴하는 andThen, compose 두가지 디폴트 메서드를 제공한다
  • andThen은 함수적용 결과를 다른 함수 입력으로 전달하는 함수를 반환한다
    andThen메서드 사용 예제
    1
    2
    3
    4
    5
    6
    7
    Function<Integer, Integer> f = x -> x + 1;
    Function<Integer, Integer> g = x -> x * 2;

    //수학적으로는 write g(f(x)) 혹은 (g o f)(x)라고 표현한다
    Function<Integer, Integer> h = f.andThen(g);

    int result = h.apply(1); // result = 4
  • compose는 인수로 주어진 함수를 먼저 실행한 다음 그 결과를 외부 함수의 인수로 제공
    compose메서드 사용 예제
    1
    2
    3
    4
    5
    6
    7
    Function<Integer, Integer> f = x -> x + 1;
    Function<Integer, Integer> g = x -> x * 2;

    //수학적으로는 write f(g(x)) 혹은 (f o g)(x)라고 표현한다
    Function<Integer, Integer> h = f.compose(g);

    int result = h.apply(1); // result = 3
  • 이 조합을 이용해서 다양한 파이프라인을 만들 수 있다

3.9 비슷한 수학점 개념

  • 프로그래밍과 상관없지만 색다른 재미를 위한 수학점 개념

  • 다음과 같은 함수(자바 함수 말고 수학함수)가 있다고 하자
    $$ f(x) = x + 10 $$

  • x가 3에서 7까지일떄의 넓이는 해당 함수의 적분을 의미한다. 이는
    $$ \int_{3}^{7}f(x)dx $$
    혹은
    $$ \int_{3}^{7}(x+10)dx $$
    으로 표현할 수 있다

그림으로 보면 옆으로 봤을때 사다리꼴이므로
$$ (사다리꼴의 넓이)={(윗변)+(아랫변)}×(높이)÷2 $$
의 공식에 의해서 구할 수 있다

$$ \frac{1}{2} \times ((3+10)+(7+10)) \times (7-3) = 60 $$

이 수학공식을 어떻게 자바로 표현할 수 있을까?
특히 적분기호$$ \int_{} $$ 나 $$ \frac{dy}{dx} $$ 등의 기호를 어떻게 처리할 것인가?

  • 함수구현
    • f와 한계값(여기서 3,7)을 인자로 받는 integrate라는 함수를 만든다고 하자
      • 다음처럼 함수 f를 인수로 받는 함수를 자바로 구현할 수 있다
        • integrate(f,3,7)
      • 하지만 다음처럼 간단히 구현은 불가능하다
        • integrate(x+10,3,7)
        • x의 범위가 불분명하다
        • 또한 f를 전달하는 것이 아닌 x+10이라는 value를 전달하게 되기에 안된ㄷ다
    • 수학에서 dx의 정체는 ‘x를 인수로 받아 x+10의 결과로 만드는 함수’로 정리

3.9.2 자바 8 람다로 연결

  • 지금까지 보았듯이 자바8에서는 람다 표현식을 사용할 수 있기에 다음 구현이 가능하다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //함수 f를 람다 표현식으로 구현
    integrate((double x) -> x+10, 3, 7)
    //다음과 같이도 불가능하다
    integrate((double x) -> f(x), 3, 7)


    //static 메서드 f를 포함하는 C클래스가 있다고 가정하면
    //메서드 레퍼런서를 사용해서 더 간단하게도 만들 수 있다.
    //f의 코드를 integrate 로 전달하는 것이 핵심이다
    integrate(C::f, 3,7)
  • 이제 integrate메서드를 어떻게 구현할 것인가? f는 선형 함수(직선)이라 가정한다
    틀린 코드! 수학식처럼 구현은 불가능! 함수형인터페이스에서 람다 사용해야함
    1
    2
    3
    public double integrate((double -> double)f, double a, double b){
    return (f(a)+f(b))*(b-a)/2.0
    }
  • 함수형 인터페이스(여기서는 Function)를 기대하는 컨텍스트에서만 람다 표현식을 사용 가능하므로 다음처럼 구현해야 한다
    람다표현식을 사용한 올바른 예시
    1
    2
    3
    public double integrate(DoubleFunction<Double> f, double a, double b){
    return (f.apply(a)+f.apply(b)) * (b-a) / 2.0;
    }
  • 수학처럼 f(a)로 표현할 수 없고 f.apply(a)라고 구현하였다
    • 자바가 진정으로 함수를 허용하지 않고 모든 것을 객체로 여기는 것을 포기 못함

3.10 정리

  • 람다 표현식은 익명 함수의 일종이다. 이름은 없지만 파라미터 리스트, 바디, 반환형식을 가지며 예외를 던질 수 있다
  • 람다 표현식으로 간결한 코드를 구현할 수 있다
  • 함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스다
  • 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다
  • 람다 표현식을 이용해서 함수형 인터페이스의 추상 메서드를 즉석으로 제공할 수 있으며 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다
  • java.util.function 패키지는 Predicate, Function<T,R>, Supplier, Consumer, BinaryOperator등을 포함해 자주 사용하는 다양한 함수형 인터페이스를 제공한다
  • 자바 8은 Predicate, Function<T,R>같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을 피할 수 있도록 IntPredicate, IntToLongFunction등과 같은 기본형 특화 인터페이스도 제공한다
  • 실행 어라운드 패턴(자원할당, 자원정리등 코드 중간에 실행해야하는 메서드에 꼭 필요한 코드)을 람다와 활용하면 유연성과 재사용성을 추가로 얻을 수 있다
  • 람다 표현식의 기대 형식을 대상 형식(target type)이라고 한다
  • 메서드 레퍼런스를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있다
  • Comparator, Predicate, Function같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다
1

Related POST

공유하기