자바 8 in action

Chapter 1

1.1.2 스트림 처리

  • 스트림
    • 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임
  • Java 스트림API
    • 조립 라인 처럼 어떤 항목을 연속적으로 제공하는 어떤 기능

1.1.3 동작 파라미터화로 메서드에 코드 전달하기

  • 동작 파라미터화
    • 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록
    • 메서드를 다른 메서드의 인수로 넘겨주는 기능

1.1.4 병렬성과 공유 가변데이터

  • pure 함수, side-effect-free 함수, stateless 함수
    • 다른 코드와 동시에 실행하더라도 안전하게 실행할 수 있는 코드
    • 공유된 가변 데이터에 접근 X

1.2.1 메서드와 람다를 일급 시민으로

  • 일급 시민
    • 프로그램을 실행하는 동안 전달할 수 있는 값
  • 람다
    • 익명 함수

1.2.2 코드 넘겨주기

  • Predicate(프레디케이트)
    • 인수로 값을 받아 true나 false로 반환하는 함수

1.2.3 메서드 전달에서 람다로

  • 한 번만 사용할 메서드는 따로 정의를 구현할 필요가 없다
  • 하지만 람다가 몇 줄 이상 길어진다면(복잡해 진다면) 코드가 수행하는 일을 잘 설명하는 이름을 가진 메서드를 정의하고 레퍼런스를 활용하는 것이 바람직하다
    • 코드의 명확성이 우선

1.3 스트림

  • External iteration(외부 반복)
    • for-each 루프(for, while)를 이용해서 각 요소를 반복하며 작업 수행
  • Internal iteration(내부 반복)
    • 라이브러리 내부에서 모든 데이터가 처리

1.3.1 멀티스레딩은 어렵다

  • 자바 8은 스트림API로 아래 두 문제를 모두 해결했다
    • 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제
    • 멀티코어 활용 어려움
  • 라이브러리가 데이터 처리의 반복되는 패턴을 제공한다면 좋을 것
    • 이를 테면 데이터를 필터링 (ex. 무게에 따른 사과 선택)
    • 데이터를 추출 (ex. 각 사과의 무게를 추출)
    • 데티러를 그룹화 (ex. 리스트의 숫자를 홀수와 짝수로 그룹화)
  • 구글 검색 같은 방식
    • Forking step(포킹 단계)
      • ex. 두 CPU를 가진 환경에서 리스트를 필터링할 때
      • 한 CPU는 앞부분을 처리하고 다른 CPU는 뒷부분을 처리하도록 요청
    • 그리고 각각 CPU는 자신이 맡은 절반의 리스트를 처리
    • 마지막으로 하나의 CPU가 두 결과를 정리
  • 컬렉션은 어떻게 데이터를 저장하고 접근할지에 포커스
  • 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 포커스

1.4 디폴트 메서드

  • 디폴트 메서드란
    • 인터페이스가 포함하는 메서드 이나 구현 클래스에서 구현하지 않아도 되는 메서드
    • 메서드 바디가 인터페이스에 포함
  • 문제
    • 하나의 클래스에서 여러 인터페이스를 구현할 수 있는데
    • 그럼 여러 인터페이스에 다중 디폴트가 메서드가 존재할 수 있다
    • 이는 어느정도 다중 상속이다
    • 다중 상속에 따르는 문제가 발생할 수 있음

Chapter 2

동작 파라미터화

  • 리스트의 모든 요소에 어떤 동작을 수행할 수 있음
  • 리스트 관련 작업을 끝낸 다음에 어떤 다른 동작을 수행할 수 있음
  • 에러가 발생하면 정해진 어떤 다른 동작을 수행할 수 있음

2.1.2

  • 소포트웨어 공학의 DRY
    • Don’t repeat yourself
    • 같은 것을 반복하지 말 것

2.2

  • 전략 디자인 패턴
    • 각 알고리즘(전략)을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법

Chapter 3 (~ 92p)

  • 람다 표현식
    • 익명클래스와 비슷한 것
    • 메서드로 전달할 수 있는 익명 함수를 단순화 한 것

3.1 람다란 무엇인가?

  • 람다 특징
    • 익명
    • 함수
      • 메서드처럼 특정 클래스에 종속되지 않는다
      • 하지만 메서드처럼 파라미터 리스트, body, return, 가능한 예외리스트를 포함한다
    • 전달
      • argunmet로 전달하거나 변수로 저장할 수 있다
    • 간결성
Comparator<Apple> byWeight = new Comparator<Apple>() {
    @Override
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
};

// use lambda
Comparator<Apple> byWeightUseLambda = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

// use intellij
Comparator<Apple> byWeightUseIntellij = Comparator.comparing(Apple::getWeight);
  • 람다 구성요소
    • 파라미터 리스트
      • 메서드의 파라미터(ex. Comparator의 compare 메서드의 a1, a2)
    • 화살표
      • 화살표(->)는 람다의 파라미터 리스트와 바디를 구분
    • 람다의 바디
      • return value
  • 예제들
    • F3이나 F4는 functional interface 이고 compile error를 피하기 위해 사용한 것 뿐
    • 이 예제에서는 equal(“=”) 이후 뒷부분만 신경쓰면 됨
@Test
public void lambdaExpressions() {
    Function<String, Integer> f1 = (String s) -> s.length();
    Function<Apple, Boolean> f2 = (Apple a) -> a.getWeight() > 150;
    F3 f3 = (x, y) -> {
        System.out.println("Result:");
        System.out.println(x+y);
    };
    F4 f4 = () -> 42;

    Comparator<Apple> byWeightUseLambda = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
}

3.2 어디에, 어떻게 람다를 사용할까?

List<Apple> greenApples = Apple.filter(inventory, (Apple a) -> "green".equals(a.getColor()));

3.2.1 함수형 인터페이스

  • 오직 하나의 추상 메서드만 지정하는 인터페이스
    • 그 하나의 메서드는 파라미터화가 가능
    • ex. Comparator, Runnable 등
@Test
public void runnableTest() {
    // use Lambda
    Runnable r1 = () -> System.out.println("Hello World 1");

    // use Anonymous Class
    Runnable r2 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World 2");
        }
    };
    process(r1);
    process(r2);
    process(()-> System.out.println("Hello World 3"));
}

private static void process(Runnable r) {
    r.run();
}

3.2.2 함수 디스크립터

  • Signatrue(시그너처)
public double getMyFundsFromBank(String bankName)
  • 요런 부분이 메소드 시그너처.
  • 이름과 return, argument등 메소드만의 정체성.

  • Function descriptor(함수 디스크립터)
    • 람다 표현식의 시그너처를 서술하는 메서드
    • 따라서 추상 메서드의 시그너처Function descriptor가 같다면, 함수형 인터페이스를 활용할 수 있겠죠

@FunctionalInterface는 무엇인가

  • 앞서 말한 함수형 인터페이스가 아닐 경우 컴파일 에러를 발생시킬 뿐
    • ex. 추상메서드가 1개 이상이면 에러발생
    • 위 어노테이션은 별게 아니었다

3.3 람다 활용 : 실행 어라운드 패턴

  • Execute around pattern (실행 어라운드 패턴)
    • 실제 자원을 처리하는 코드를 설정(set up)정리(clean up) 두 과정이 둘러싸는 형태
    • 예를 들어 자원처리에 사용되는 패턴은
      • 자원을 열고
      • 처리한 다음에
      • 자원을 닫는 순서로 이루어짐
@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

public class ExecuteAroundPattern {
    public static String processFile(BufferedReaderProcessor p) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
            return p.process(br);
        }
    }
}

위와 같이 구현하면, 람다를 이용해서 다양한 동작을 processFile에 메서드로 전달할 수 있다

@Test
public void processFileTestSpec() throws IOException {
    String oneLine = processFile((BufferedReader br) -> br.readLine());
    String twoLines = processFile((br) -> br.readLine() + br.readLine());
    System.out.println(oneLine);
    System.out.println(twoLines);
}

3.4 함수형 인터페이스 사용

  • 함수형 인터페이스의 추상 메서드는 람다 표현식의 시그너처를 묘사한다

3.4.1 Predicate

  • test라는 추상 메서드
    • T -> boolean

3.4.2 Consumer

  • accept라는 추상 메서드
    • T -> void
@Test
public void functionalInterfaces() {
    Predicate<String> nonEmptyStringPredicate = (s) -> !s.isEmpty();
    predicateExample(Arrays.asList("a", "b", "c", "", "e"), nonEmptyStringPredicate);
    consumerExample(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println("hello" + i));
}

private static void predicateExample(List<String> ss, Predicate<String> p) {
    for (String s : ss) {
        if(p.test(s)) System.out.println(s);
    }
}

private static void consumerExample(List<Integer> ii, Consumer<Integer> c) {
    for (Integer i : ii) {
        c.accept(i);
    }
}
실행결과
a
b
c
e
hello1
hello2
hello3
hello4
hello5

3.4.3 Function

  • 제너릭 형식 T를 인수로 받아, 제너릭 형식 R 객체를 반환하는 apply 추상메서드
    • T -> R
@Test
public void functionTest() {
    System.out.println(map(Arrays.asList("lambda", "in", "action"), (String s) -> s.length()));
}

private static <T, R> List<R> map(List<T> list, Function<T, R> f) {
    List<R> result = new ArrayList<>();
    for (T t : list) {
        result.add(f.apply(t));
    }
    return result;
}
실행결과
[6, 2, 6]
  • 제너릭은 참조형만 사용할 수 있다
    • 프리미티브 타입(기본형)은 사용 불가
    • 오토박싱이 이루어질 때에도 비용이 소모됨
      • 래퍼로 감싸며 힙에 저장됨
      • 박싱한 값은 기본형을 가져올 때에도 메모리를 탐색하는 과정이 필요
      • 오토박싱을 피하면 좋겠지?
      • 그래서 함수형 인터페이스에 기본형 특화 인터페이스들도 존재
      • ex. Predicate
        • IntPredicate, LongPredicate, DoublePredicate
  • 대표적으로 위 세개 외에
    • Supplier
      • () -> T
      • 객체 생성 시
    • BiFunction<T, U, R>
      • (T, U) -> R
      • 두 객체 비교 시

3.5 형식 검사, 형식 추론, 제약

  • Target type (대상 형식)
    • 어떤 콘텍스트에서 기대되는 람다 표현식의 형식

3.5.4 지역 변수 사용

  • Capturing lambda(람다 캡쳐링)
    • 람다 표현식에서는 자유변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있다
    • Free variable
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
  • 캡쳐링 할 수 있는 자유 변수에는 제약이 있다
    • final로 선언되어 있거나
    • 실질적으로 final 처럼 사용되어야됨
    • 즉 변수가 아니라 상수를 캡쳐링 할 수 있는 느낌

3.6 메서드 레퍼런스

  • 메서드 레퍼런스를 만드는 방법
    1. 정적 메서드 레퍼런스
      • Integer.parseInt() -> Integer::parseInt
    2. 다양한 형식의 인스턴스 메서드 레퍼런스
      • String s, s.length() -> String::length
    3. 기존 객체의 인스턴스 메서드 레퍼런스
      • T a = new T(), T라는 객체에는 getValue() 메서드가 있다면
      • a::getValue 가능

Quiz 3-6 메서드 레퍼런스

  • 람다 표현식과 일치하는 메서드 레퍼런스는?
  1. Function<String, Integer> StringToInteger = (String s) -> Integer.parseInt(s);
    • Integer::parseInt
  2. BiPredicate<List, String> contains = (list, element) -> list.contains(element);
    • List::contains

3.6.2 생성자 레퍼런스

@Test
public void apple() {
    Double d = 300.0;
    Apple a1 = new Apple(d);

    Function<Double, Apple> c2 = Apple::new;
    Function<Double, Apple> c3 = (weight) -> new Apple(weight);

    Apple a2 = c2.apply(110.0);
    Apple a3 = c3.apply(110.0);
}

@Test
public void apple2() {
    BiFunction<String, Double, Apple> c4 = Apple::new;
    Apple a4 = c4.apply("green", 10.0);

    BiFunction<String, Double, Apple> c5 = (s, d) -> new Apple(s, d);
    Apple a5 = c5.apply("red", 20.0);
}

3.7 람다, 메서드 레퍼런스 활용하기!

  • Apple을 무게에 따라 정렬하는 메소드를 만들어보쟈.
  • 최종목표는 아래와 같다
    • inventory.sort(comparing(Apple::getWeigth));

3.7.1 코드 전달

public class AppleComparator implements Comparator<Apple> {
    @Override
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
}

요롷게 하면

inventory.sort(new AppleComparator());

까지 가능

3.7.2 익명 클래스 사용

inventory.sort(new Comparator<Apple>() {
    @Override
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
});

3.7.3 람다 표현식 사용

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing(a -> a.getWeight()));
  1. 기본
  2. 형식 추론 사용
  3. 정적 메서드 Comparator.comparing 사용

3.7.4 메서드 레퍼런스 사용

import static java.util.Comparator.*;
...
inventory.sort(comparing(Apple::getWeight));
  • Apple을 weight별로 비교해서 inventory를 sort하라

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

  • 역정렬
inventory.sort(comparing(Apple::getWeight).reversed());
  • Comparator 연결
inventory.sort(comparing(Apple::getWeight)
         .reversed());
         .thenComparing(Apple::getCountry);

thenComparing(java.util.Comparator)은 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면 두 번째 비교자에 객체를 전달한다.

3.8.2 Predicate 조합

  • negate()
    • 특정 프레디케이트를 반전
  • and() , or()
Predicate<Apple> redAndHeavyAppleOrGreen =
    redApple.and(a -> a.getWeight() > 150)
            .or(a -> "green".equals(a.getColor()));

3.8.3 Function 조합

  • andThen()
    • 함수 f 먼저 실행한 이후에 함수 g를 실행하게끔 가능
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);

int result = h.apply(1);    // (1+1) * 2 = 4
  • compose()
    • f.compose(g)
    • 주어진 함수 g를 실행한 다음에 그 결과를 함수 f의 인수로 제공
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);

int result = h.apply(1);    // 1 + (1*2) = 3

Chapter 4 스트림 소개

4.1 스트림이란 무엇인가?

  • 스트림을 이용하면
    • 선언형(데이터를 처리하는 임시 구현 코드 대신 질의로 표현가능)으로 컬렉션 데이터를 처리할 수 있다
    • 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다
  • 아래는 자바 7 코드
public void findLowCalories() {
    List<Dish> menu = 
            new ArrayList<>(Arrays.asList(
                    new Dish("a", 500), 
                    new Dish("b", 300), 
                    new Dish("c", 700), 
                    new Dish("d", 100)));
    List<Dish> lowCaloricDishes = new ArrayList<>();    // garbage variable
    for (Dish d : menu) {
        if(d.getCalorie() < 400) {
            lowCaloricDishes.add(d);
        }
    }

    // sortWithAnonymousClass
    Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
        @Override
        public int compare(Dish d1, Dish d2) {
            return Integer.compare(d1.getCalorie(), d2.getCalorie());
        }
    });

    // getNameFromList
    List<String> lowCaloricDishesName = new ArrayList<>();
    for (Dish d : lowCaloricDishes) {
        lowCaloricDishesName.add(d.getName());
    }
    System.out.println(lowCaloricDishesName);
}
  • 주석으로도 명시해 놓았지만 List<Dish> lowCaloricDishes = new ArrayList<>(); 부분이 컨테이너 역할만 하는 중간 변수 즉 ‘가비지 변수’ 이다

  • 자바 8 스트림을 사용한 코드

public void useStream() {
    List<Dish> menu =
            new ArrayList<>(Arrays.asList(
                    new Dish("a", 500),
                    new Dish("b", 300),
                    new Dish("c", 700),
                    new Dish("d", 100)));

    List<String> lowCaloricDishesName = menu.parallelStream() // menu.stream()
            .filter(d -> d.getCalorie() < 400)
            .sorted(Comparator.comparing(Dish::getCalorie))
            .map(Dish::getName)
            .collect(Collectors.toList());

    System.out.println(lowCaloricDishesName);
}
  • stream() 대신 parallelStream() 을 사용하면 이 코드가 멀티코어 아키텍처에서 병렬로 실행가능하다

  • 선언형
    • 루프와 if 조건문 등의 제어를 사용하여 어떻게 동작을 하라가 아니라
    • 저칼로리의 요리만 선택하라 같은 동작수행을 지정할 수 있다
  • filter -> sorted -> map -> collect
    • 고수준 빌딩 블록
  • 데이터 처리과정을 병렬화 하면서 스레드와 락을 걱정할 필요가 없다

  • 특징 요약
    • 선언형 : 간결, 가독성 향상
    • 조립가능 : 유연성이 향상
    • 병렬화 : 성능 향상

4.2 스트림 시작하기

  • 스트림이란
    • 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
  • 연속된 요소
    • 컬렉션과 마찬가지로 연속된 값 집합의 인터페이스
    • 컬렉션은 자료구조 이므로 시간과 공간의 복잡성, 관련된 요소 저장 및 접근 연산이 주
    • 스트림은 filter, sorted, map처럼 표현 계산식이 주
    • 즉, 컬렉션의 주제는 데이터고 스트림의 주제는 계산이다
  • 소스
    • 스트림은 컬렉션, 배열, I/O자원 등의 데이터 제공 소스로부터 데이터를 소비한다
  • 데이터 처리 연산

  • 파이프라이닝

  • 내부 반복
List<String> threeHighCaloricDishNames =
        menu.stream()
        .filter(d -> d.getCalorie() > 300)
        .map(Dish::getName)
        .limit(3)
        .collect(Collectors.toList());

System.out.println(threeHighCaloricDishNames);
  • menu.stream() 메소드를 호출하여 menu로부터 스트림을 얻음
    • 데이터 소스는 menu
    • 데이터 소스는 연속된 요소를 스트림에 제공
  • filter, map, limit, collect 데이터 처리 연산
  • filter, map, limit은 서로 파이프라인을 형성할 수 있도록 스트림을 반환한다

  • filter
    • 람다를 인수로 받아 스트림에서 특정 요소를 제외시킨다
  • map
    • 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출한다
  • limit
    • 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 스트림 크기를 축소한다
  • collect
    • 스트림을 다른 형식으로 변환한다

4.3 스트림과 컬렉션

  • 데이터를 언제 계산하느냐가 가장 큰 차이
    • 컬렉션
      • 컬렉션에 추가되기 전에 계산되어야 한다
      • ex) dvd에 저장된 영화
    • 스트림
      • 이론적으로 요청할 때만 요소를 계산한다
      • 스트림에 요소를 추가하거나 요소를 제거할 수 없다
      • ex) 인터넷 스트리밍 영화

4.3.1 딱 한번만 탐색할 수 있다

  • 탐색된 스트림의 요소는 소비된다
  • 한번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 한다

4.3.2 외부 반복과 내부 반복

  • 외부반복
List<String> names = new ArrayList<>();
for (Dish d : menu) {
    names.add(d.getName());
}
  • 내부반복
List<String> names =
        menu.stream()
        .map(Dish::getName)
        .collect(Collectors.toList());

4.4 스트림 연산

  • 스트림의 연산은 크게 두 가지로 분류 가능하다

4.4.1 중간연산

  • 연결할 수 있는 스트림 연산

  • 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다

    • lazy
List<String> names =
        menu.stream()
                .filter(d -> {
                    System.out.println("filtering" + d.getName());
                    return d.getCalorie() > 300;
                })
                .map(d -> {
                    System.out.println("mapping" + d.getName());
                    return d.getName();
                })
                .limit(3)
                .collect(Collectors.toList());
System.out.println(names);

실행결과

filteringpork
mappingpork
filteringbeef
mappingbeef
filteringchicken
mappingchicken
[pork, beef, chicken]
  • filter와 map이 서로 다른 연산이지만 한 과정으로 병합되어 섞여 나오는 것을 확인할 수 있다
    • 이걸 loop fusion(루프 퓨전) 이라고 한다고.. ㅎㅎ

4.4.2 최종 연산

  • 스트림을 닫는 연산

  • forEach

    • 각 소스에 람다를 적용한 다음 void를 반환

4.4.3 스트림 이용하기

  • 스트림 이용과정은 다음 세 가지로 요약 가능하다

  • 질의를 수행할 데이터 소스
  • 스트림 파이프라인을 구성할 중간 연산 연결
  • 스트림 파이프라인을 실행하고 결과를 만들 최종 연산

  • 스트림 파이프라인의 개념은 builder pattern과 유사

Chapter 5 스트림 활용

5.1 필터링과 슬라이싱

5.1.1 프레디케이트로 필터링

List<Dish> vegetarianMenu = menu.stream()
                .filter(Dish::isVegetarian)
                .collect(Collectors.toList());

5.1.2 고유 요소 필터링

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0)
        .distinct()
        .forEach(System.out::println);
  • distinct를 안쓰고 .collect(Collectors.toSet()) 도 가능하다

5.1.3 스트림 축소

List<Dish> dishes = menu.stream()
                .filter(d -> d.getCalorie() > 300)
                .limit(3)
                .collect(Collectors.toList());
  • predicate와 일치하는 처음 세 요소

5.1.4 요소 건너뛰기

List<Dish> dishes = menu.stream()
                .filter(d -> d.getCalorie() > 300)
                .skip(2)
                .collect(Collectors.toList());
  • predicate와 처음 일치하는 두 요소를 제외
  • skip과 limit은 상호보완

5.2 매핑

  • 함수를 argument로
  • argument로 제공된 함수는 각 요소에 적용, 적용된 결과가 새로운 요소에 매핑
  • 기존의 값을 고친다 라는 개념보다는 새로운 버전을 만든다

5.2.1 스트림의 각 요소에 함수 적용하기

  • 새로운 요리 이름 리스트 생성
List<String> dishNames = menu.stream()
        .map(Dish::getName)
        .collect(Collectors.toList());
  • 단어의 글자 수로 된 새로운 리스트 생성
List<String> words = Arrays.asList("Java8", "Lambda", "In", "Action");
List<Integer> wordLengths = words.stream()
        .map(String::length)
        .collect(Collectors.toList());
  • 요리 이름의 글자 수로 된 새로운 리스트 생성
    • chaining
List<Integer> dishNames = menu.stream()
        .map(Dish::getName)
        .map(String::length)
        .collect(Collectors.toList());

5.2.2 스트림 평면화

  • [“Hello”, “World”] 리스트로
  • 만들기
List<String> words = Arrays.asList("Hello", "World");
words.stream()
        .map(word -> word.split(""))
        .distinct()
        .collect(Collectors.toList());
  • 요론식이 가능하다 근데 map이 반환하는건 Stream<String[]> 이다
    • Stream을 만들고 싶다
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
  • 이걸 위에 적용하면
words.stream()
        .map(word -> word.split(""))
        .map(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());
  • 이거인데 이건 Stream<Stream>이 되어버렸다
  • 해결법
List<String> words = Arrays.asList("Hello", "World");
List<String> list = words.stream()
        .map(word -> word.split(""))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());
  • flatMap을 사용하면 된다
  • flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다
  • 다시말해 flatMap은 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다

5.3 검색과 매칭

5.3.1 프레디케이트가 적어도 한 요소와 일치하는지 확인

if(menu.stream().anyMatch(Dish::isVegetarian)) {
    System.out.println("any");
}
  • anyMatch는 boolean을 반환하는 최종연산이다

5.3.2 프레디케이트가 모든 요소와 일치하는지 검사

if(menu.stream().allMatch(Dish::isVegetarian)) {
    System.out.println("all");
}
  • noneMatch는 allMatch와 정 반대이다
  • 주어진 프레디케이트와 일치하는 요소가 없어야 한다
  • anyMatch, allMatch, noneMatch는 쇼트서킷 기법을 활용
    • &&,   같은 연산
    • and 하다가 하나만 false 여도 결과를 알 수 있듯!

5.3.3 요소 검색

  • findFirst, findAny는 둘 다 요소를 찾아주는 애
    • 병렬 실행에서는 첫번째 요소를 찾기가 어려워 findAny를 사용한다

5.4 리듀싱

  • 모든 스트림 요소를 처리해서 값으로 도출하는 과정
    • 종이를 작은 조각이 될 때 까지 접는 것과 비슷하다 해서 fold라고도 부른다
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
  • 위와 같이 스트림의 모든 요소를 더할 수 있다
    • arg1 : 초기값 0
    • arg2 : 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator
  • 메서드 레퍼런스 사용
int sum2 = numbers.stream().reduce(0, Integer::sum);
  • arg1의 초깃값을 없이도 사용이 가능하나
    • 그럴 경우 아무 요소도 없는 상황에서 null이기에
    • return 이 Optional이다
Optional<Integer> max = numbers.stream().reduce(Integer::max);
  • 생각해야할 문제 내부 연산을 갖는 연산, stateful operation
    • reduce 같은 연산은 내부 상태를 갖는다
      • 같은 연산 결과를 내부에 누적해야 한다
    • sorted나 distinct 연산도 마찬가지로 과거 이력을 알고 있어야 한다
      • 어떤 걸 정렬하려면 모든 요소가 버퍼에 추가되어야 한다
    • 필요한 저장소 크기는 정해져 있지 않다
    • 그래서 스트림의 크기가 엄청 크거나 무한이라면 문제가 생길 수 있다

5.6 숫자형 스트림

  • reduce로 int 합을 구할 경우 boxing 비용이 소모된다
    • 효율적으로 처리할 수 있도록 기본형 특화 스트림을 제공한다

5.6.1 기본형 특화 스트림

int calories = menu.stream()
        .mapToInt(Dish::getCalorie)
        .sum();
  • 기본형 특화 스트림 -> 객체 스트림으로 복원하기
IntStream intStream = menu.stream().mapToInt(Dish::getCalorie);
Stream<Integer> stream = intStream.boxed();
  • Optional도 기본현 특화 스트림 버전을 제공한다
OptionalInt maxCalories = menu.stream()
        .mapToInt(Dish::getCalorie)
        .max();
int max = maxCalories.orElse(1);

5.6.2 숫자 범위

  • IntStream과 LongStream에서는 rangerangeClosed라는 static method를 제공한다
    • range는 arg1 초과 arg2 미만
    • rangeClosed는 arg1 이상 arg2 이하
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
                .filter(n -> n % 2 == 0);

5.7 스트림 만들기

  • 크기가 정해지지 않은 스트림을 무한 스트림이라 한다
    • Stream.iterate와 Stream.generate를 제공한다