모던 자바 인 액션 Chap 5

doxxx·2022년 5월 16일
0

모던자바인액션

목록 보기
2/14
post-thumbnail

Chapter 5. 스트림 활용

5.1 필터링

Predicate로 필터링

  • 스트림 인터페이스는 filter를 지원한다. filter 메서드는 Predicate(Boolean을 리턴하는 함수)를 인수로 받아 일치하는 모든 요소를 포함하는 스트림을 반환한다.
  • 고유요소로 필터링을 하기 위해선 distinct 메소드를 사용한다. (hashcode, equals로 결정)

5.2 스트림 슬라이싱

TakeWhile 활용 - java9

  • 만약 필터링을 할 대상이 이미 정렬이 되어 있는 대상이라면 filter를 사용하기 보다는 takewhile을 사용하는것을 권장.
  • predicate를 만족하지 않는 첫번째 지점부터 마지막 스트림을 버린다.
  • takeWhile은 filter + break 라고 정의할 수있다.
  // 전체 리스트 순회
  List<Car> filterCar = carList.stream()
                            .filter(car -> car.getPrice() < 3000)
                            .collect(toList());
        
  // 조건에 맞지 않으면 Stop
  List<Car> filterCar = carList.stream()
                            .takeWhile(car -> car.getPrice() < 3000)
                            .collect(toList());

DropWhile

  • 나머지 요소를 선택하려면 DropWhile을 사용할 수 있다.
  • predicate를 처음으로 만족하지 지점까지의 요소를 버리는 작업을 진행.
// 처음으로 거짓이 되는 지점까지 버림.
  List<Car> filterCar = carList.stream()
                            .dropWhile(car -> car.getPrice() < 3000)
                            .collect(toList());

스트림 축소

스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원한다.

// 3개 요소 반환
  List<Car> filterCar = carList.stream()
                            .filter(car -> car.getPrice() < 3000)
                            .limit(1)
                            .collect(toList());

요소 건너뛰기

  • 스트림은 처음 n개의 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다.
  • n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.
// 처음 3개 요소 건너뛰기
  List<Car> filterCar = carList.stream()
                            .filter(car -> car.getPrice() < 3000)
                            .skip(3)
                            .collect(toList());

5.3 매핑

  • 특정 객체에서 특정 데이터를 선택하는 작업은 데이터 처리 과정에서 자주 수행되는 연산이다.
  • 스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능을 제공한다.

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

  • 스트림은 함수를 인수로 받는 map 메서드를 지원한다. 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.
// 자동차 이름을 추출하는 예제
List<String> carNames = car.stream()
                           	.map(Car::getName)
                           	.collect(toList());
                            
// 단어 리스트의 글자수 추출
List<String> words = Arrays.asList("모자인", "스터디", "화이팅");
List<Integer> wordLengths = words.stream()
								.map(String::length)
							    .collect(toList());

// map 을 연결하여 자동차 이름 글자수 추출
List<String> carNames = car.stream()
                           .map(Car::getName)
                           .map(String::length)
                           .collect(toList());  

스트림 평면화

  • 이전 예제를 사용하여 리스트에서 고유문자로 이루어진 리스트를 반환 해보자.
    - 예를들어 [“Hello”, “World”] 리스트가 [“h”, “e”, “l”, “o”, “W”, “r”, “d”] 가 되도록 변경하는 것이다.
words.stream()
        .map(word -> word.split(""))
        .distinct()
        .collect(tolist())
  • 위와 같이 실행한다면 map의 결과값으로 Stream<String[]>이 될것이다. 원하는 결과는 Stream인데 어떻게 구현헤야 할지 생각 해보자.
  • 방법을 모른다면 간단하지가 않다. flatMap은 사용해 문제를 해결할수 있다.
List<String> uniqueCharacters = words.stream()
          .map(word -> word.split("")) // 각 단어를 개별 문자를 포함하는 배열로 변환 
          .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
          .distinct()
          .collect(toList());
  • flatMap은 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다. 즉, map(Arrays::stream)과 달리 flatMap은 하나의 평면화된 스트림을 반환한다.
  • 요약하면 flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다.

5.4 검색과 매칭

  • 특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리도 자주 사용된다.

요소 매칭

스트림은 allMatch, anyMatch, noneMatch, findFirst, findAny등 다양한 유틸리티 메서드를 지원한다.

  • anyMatch: Predicate가 적어도 한 요소와 일치 하는지 확인
    - boolean isMatch = menu.stream().anyMatch(Dish::isVegetarian)
    • boolean값을 반환 하기 때문에 최종연산에 포함된다.
  • allMatch: Predicate가 모든 요소와 일치하는지 검사
    - boolean allMatch = menu.stream().allMatch(Dish::isVegetarian)
  • NoneMatch: allMatch와 반대 연산
    - noneMatch는 allMatch와 반대 연산을 수행한다.
    - 즉. noneMatch는 주어진 predicate와 일치하는 요소가 없는지 확인한다.

요소 검색

findAny 메서드는 현재 스트림에서 임의의 요소를 반환한다. findAny 메서드를 다른 스트림 연산과 연결해서 사용할 수 있다.

  // filter 와 findAny를 활용해 채식 요리를 선택하는 방법
  Optional<Dish> dish = words.stream()
          .filter(Dish::isVegetarian)
          .findAny();

Optional은 10장에서~

첫 번째 요소 찾기

리스트 또는 정렬된 데이터로 부터 생성된 스트림은 논리적인 아이템 순서가 정해져 있을 수 있다. 이런 스트림에서 첫번째 요소를 찾기 위한 방법은 findFirst를 사용하는 것이다.

//  3으로 나누어 떨어지는 첫 번째 제곱근 값을 반환하는 코드.
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> first = someNumbers.stream()
        							.map(n -> n * n)
							        .filter(n -> n % 3 == 0)
							        .findFirst();

findAny와 findFirst는 언제 사용하나 ?

두 개의 메서드를 모두 제공하는 이유는 병렬성 때문이다. 병렬 실행에서는 첫번째 반환 요소를 선택하기 어려우니 반환 순서가 상관이 없다면 병렬 스트림에서 제약이 적은 findAny를 주로 사용한다.

5.5 리듀싱

스트림의 최종연산 중 하나로 마지막 결과가 나올때까지 스트림의 모든 요소를 반복적으로 처리하는 과정
함수형 프로그래밍 언어 용어로는 이과정이 마치 종이를 작은 조각이 될때까지 반복해서 접는것과 비슷하다 하여 폴드라고 부른다.

요소의 합

reduce 메서드를 살펴보기 전에 for-each 루프를 이용해 리스트의 숫자 요소를 더하는 코드를 확인

int sum = 0;
  for (int x : numbers) {
    sum += x;
  }
  • numbers의 요소는 결과에 반복적으로 더해진다. 리스트에서 하나의 숫자가 남을 때 까지 reduce 과정을 반복한다.
  • 파라미터
    - sum 변수의 초깃값 0
    - 리스트의 모든 요소를 조합하는 연산 (+)
  • reduce를 사용하면 애플리케이션의 반복된 패턴을 추상화 할 수 있다.
    - int sum = numbers.stream().reduce(0, (a, b) -> a + b);
  • reduce는 두개의 인수를 갖는다.
    - 초깃값 0.
    - 두 요소를 조합해 새로운 값을 만드는 BinaryOperator. 예제에서는 람다 표현식 (a, b) -> a + b를 사용했다.
  • reduce로 다른 람다, 즉 (a, b) -> a * b를 넘겨주면 모든 요소에 곱셈을 적용할 수 있다.
  • Java 8 에서는 Integer 클래스에 두 숫자를 더하는 정적 sum 메서드를 지원하여 아래와 같이 사용할 수 있다.
    - int sum = numbers.stream().reduce(0, Integer::sum);
  • 초깃값 없음
    - 초깃값을 받지 않도록 오버로드된 reduce는 결과값으로 Optional객체를 반환한다.
    - Optional sum = numbers.stream().reduce((a, b) -> a + b);

최대값, 최솟값

  • 최대값과 최솟값을 찾는 연산도 reduce를 활용하면 쉽게 할 수 있다.
    - Optional max = numbers.stream().reduce(Integer::max);
    - Optional min = numbers.stream().reduce(Integer::min);
  • 메소드 레퍼런스 대신 람다 표현식을 사용해도 되지만 메소드 레퍼런스를 활용할 수 있으면 활용하도록 하자.
    - (x, y) -> x > y ? x : y;

5.7 숫자형 스트림

기본형 특화 스트림

  • 숫자 스트림으로 매핑
    - 스트림을 특화 스트림으로 사용할 땐 mapToInt, mapToDouble, mapToLong을 가장 많이 사용한다.
    - 이들 스트림은 map과 같은 역할을 하지만 Stream 가 아닌 특화된 스트림을 반환한다.
  int calories = menu.stream()
                      .mapToInt(Dish::getCalories)
                      .sum();
  • mapToInt 메서드는 각 요리에서 모든 칼로리를(Integer)형태로 추출한 다음 IntStream을 반환 한다. 따라서 IntStream에서 제공하는 sum과 같은 메소드를 사용할 수 있다.
  • 객체 스트림으로 복원하기
    - IntStream으로 만든 다음, 다시 Stream으로 만들기 위한 방법
    - IntStream과 같은 특화된 스트림은 각 스트림의 기본값들만 만들수 있다. IntStream의 map 연산은 int를 인수로 받아 int를 반환하는 람다 (IntUnaryOperator)를 인수로 받게 된다.
    하지만 정수가 아닌 Dish와 같은 값을 반환 하기 위해선 스트림 인터페이스에 정의된 연산을 사용해야 한다. 이런 경우 boxed 메서드를 사용할 수 있다.
  IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
  Stream<Integer> stream = intStream.boxed();

숫자 범위

  • 특정 범위의 숫자를 이용하거나 정의하기 위한 메서드
    - IntSteram.rangeClose(1, 100) // 1 ~ 100 의 숫자 범위를 만든다.
    - range()는 열려있는 연산을 하기 때문에 마지막 숫자가 범위에 포함되지 않는다.

5.8 스트림 만들기

값으로 스트림 만들기

  • Stream.of(“Modern “, “Java”, “In”, “Action”);
  • null 가능한 스트림 만들기 - Java 9부터 지원
  Stream<String> homeValueStream = Stream.ofNullable(System.getProperty("home"));
  • null이 될 수 있는 객체를 포함하는 스트림 값을 flatMap을 사용한다면 더욱 유용하게 사용할 수 있다.
Stream<String> values = Stream.of("config", "home", "user")
                                .flatMap(key -> Stream.ofNullable(System.getProperty(key)))

배열로 스트림 만들기

int[] nubmers = {1, 2, 3, 4, 5};
int sum = Arrays.stream(numbers).sum();

함수로 무한 스트림 만들기

  • iterate 사용
Stream.iterate(0, n -> n + 2)
        .limit(10)
        .forEach(System.out::println)

iterate 메서드는 초깃값과 람다를 활용하여 스트림을 생성 할 수 있다. 이때 생성되는 스트림은 무한적이기 때문에 반드시 limit과 같은 메서드를 사용하여 범위를 지정해주어야 한다.
iterate는 순차적으로 실행되며 무한적으로 생성되는 스트림을 언바운드 스트림이라고 표현한다.
Java 9 부터는 iterate 에서 Predicate를 지원한다.

  // 0 부터 시작하여 100 보다 작은 수의 스트림 생성
  Stream.iterate(0, n -> n < 100, n -> n + 2)
        .forEach(System.out::println)
  // 아래와 같은 방법은 사용할 수 없다. 스트림의 종료시점을 알수 없기 때문이다 .
  Stream.iterate(0, n -> n + 2)
        .filter(n -> n < 100)
        .forEach(System.out::println)
  // 스트림 쇼트서킷을 지원하는 takeWhile을 사용하는것이 해법이다.      
  Stream.iterate(0, n -> n + 2)
        .takeWhile(n -> n < 100)
        .forEach(System.out::println)
  • generate 사용
    iterate와 비슷하게 generate도 요구할 때 값을 계산하는 무한 스트림을 만들 수 있다.
    iterate와 차이점은 생산된 값을 연속적으로 계산하지 않고 Supplier 를 인수로 받아 새로운 값을 생성한다.
Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println)

0개의 댓글