[모던 자바 인 액션] 05. 스트림 활용

seony·2023년 3월 10일
0

모던 자바 인 액션

목록 보기
5/6
post-thumbnail

5.1 필터링

스트림의 요소를 선택하는 방법, 즉 프레디케이트 필터링 방법고유 요소만 필터링하는 방법을 배운다.

1. 프레디케이트로 필터링

  • 프레디케이트(불리언을 반환하는 함수)를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.
List<Dish> vegetarianMenu = menu.stream()
								.filter(Dish::isVegetarian)
                                .collect(toList());

2. 고유 요소 필터링

  • 스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원한다.
  • 고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
	   .filter(i -> i % 2 == 0)
       .distinct()
       .forEach(System.out::println);

5.2 스트림 슬라이싱 (자바 9)

  • 스트림의 요소를 선택하거나 스킵하는 다양한 방법을 소개한다.

1. 프레디케이트를 이용한 슬라이싱

🎈 takeWhile 활용

List<Dish> filteredMenu = specialMenu.stream()
									 .filter(dish -> dish.getCalories() < 320)
                                     .collect(toList());
  • 리스트가 이미 정렬되어 있다는 사실을 이용해 320칼로리보다 크거나 같은 요리가 나왔을 때 반복 작업을 중단할 수 있다.

<320칼로리보다 작은 요소 탐색>

List<Dish> slicedMenu1 = specialMenu.stream()
									.takeWhile(dish -> dish.getCalories() < 320)
                                    .collect(toList());

🎈 dropWhile 활용

  • dropWhiletakeWhile과 정반대의 작업을 수행한다.
  • dropWhile은 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다.
    • 즉, 320칼로리보다 작은 값들을 버리게 된다.

<320칼로리보다 큰 요소 탐색>

List<Dish> slicedMenu2 = sepcialMenu.stream()
									.dropWhile(dish -> dish.getCalories() < 320)
                                    .collect(toList());

2. 스트림 축소

🎈 limit(n)

  • 스트림이 정렬되어 있으면 최대 요소 n개를 반환할 수 있다.
    • 소스가 정렬되어 있지 않았다면 limit의 결과도 정렬되지 않은 상태
List<Dish> dishes = specialMenu.stream()
							   .filter(dish -> dish.getCalories() > 300)
                               .limit(3)
                               .collect(toList());

3. 요소 건너뛰기

  • skip(n) 메서드 지원

5.3 매핑

  • 특정 객체에서 특정 데이터를 선택하는 작업을 지원
  • map, flatMap

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

🎈 map

List<String> dishNames = menu.stream()
							 .map(Dish::getName)
                             .collect(toList());
  • 인수로 제공된 함수는 각 요소에 적용되어 함수를 적용한 결과가 새로운 요소로 매핑된다.
  • getName은 문자열을 반환 ➜ .map(Dish::getName)의 출력 스트림은 Stream<String>

2. 스트림 평면화

  • 원하는 결과
    • List<String>

<문제 발생>

words.stream()
     .map(word -> word.split("")) // Stream<String[]>
     .map(Arrays::stream) // Stream<Stream<String>>
     .distinct()
     .collect(toList()); // List<Stream<String>> -> 문제 발생

🎈 flatMap

words.stream()
	 .map(word -> word.split("")) // Stream<String[]>
     .flatMap(Arrays::stream)  // Stream<String>
     .distinct()
     .collect(toList()); // List<String>
  • flatMap
    • 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다.
    • 제공된 함수를 각 요소에 적용하여 새로운 하나의 스트림으로 매핑한다.

5.4 검색과 매칭

🎈 anyMatch

bolean anyMatch(Predicate<? super T> predicate);
  • 스트림에서 적어도 한 요소와 일치하는지 확일할 때

🎈 allMatch

boolean allMatch(Predicate<? super T> predicate);
  • 스트림의 모든 요소가 주어진 프레디케이트와 일치하는지 검사

🎈 noneMatch

boolean noneMatch(Predicate<? super T> predicate);
  • 주어진 프레디케이트와 일치하는 요소가 없는지 확인

쇼트서킷 기법

anyMatch, allMatch, noneMatch 세 메서드는 스트림 쇼트서킷 기법이다.
전체 스트림을 처리하지 않았더라고 결과를 반환할 수 있는 것을 의미한다.

🎈 findAny

Optional<T> findAny()
  • 현재 스트림에서 임의의 요소를 반환

🎈 findFirst

Optional<T> findFirst()
  • 첫 번째 요소를 찾을 때 사용

5.5 리듀싱

1. 요소의 합

반복문 사용

int sum = 0
for (int x : numbers) {
	sum += x;
}    

🎈 reduce 사용

int sum = numbers.stream().reduce(0, (a, b) -> a + b);
  • reduce는 두 개의 인수를 갖는다.
    • 초깃값 0
    • 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T>
int sum = numbers.stream().reduce(0, Integer::sum);

초깃값 없음

Optional<Integer> sum = nubmers.stream().reduce((a, b) -> (a + b));
  • Optional<Integer>을 반환?
    • 만약 빈 스트림이라면 초깃값이 없으므로 값을 반환할 수 없기 때문이다.

2. 최댓값, 최솟값

Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
  • 스트림은 최댓값이나 최솟값을 계산하는 데 사용할 키를 지정하는 Comparator를 인수로 받는 minmax 메서드를 제공한다.
Optional<Transaction> smallestTransaction = transaction.stream()
													   .min(comparing(Transaction::getValue));

💡 reduce 메서드의 장점과 병렬화

  • reduce를 이용하면 내부 반복이 추상화되면서 내부 구현에서 병렬로 reduce를 실행할 수 있게 된다.
  • 반복적인 합계에서는 sum 변수를 공유해야 하므로 쉽게 병렬화하기 어렵다.

💡 스트림 연산 : 상태 없음과 상태 있음

  • map, filter 등은 입력 스트림에서 각 요소를 받아 0 또는 결과를 출력 스트림으로 보낸다. 따라서 이들은 보통 상태가 없는, 즉 내부 상태를 갖지 않는 (stateless operation)이다.
  • 하지만 reduce, sum, max 연산은 결과를 누적할 내부 상태가 필요하다. 예제의 내부 상태는 작은 값이다. 스트림에서 처리하는 요소 수와 관계없이 내부 상태의 크기는 한정 (bounded)되어 있다.
  • 반면 sorteddistinct 같은 연산은 스트림의 요소를 정렬하거나 중복을 제거하기 위해 과거의 이력을 알고 있어야 한다. 예를 들어 어떤 요소를 출력 스트림으로 추가하려면 모든 요소가 버퍼에 추가되어 있어야 한다. 연산을 수행하는 데 필요한 저장소 크기는 정해져있지 않다. 따라서 데이터 스트림의 크기가 크거나 무한이라면 문제가 생길 수 있다. 이러한 상태를 내부 상태를 갖는 연산(stateful operation)이라 한다.

5.6 실전 연습

  • 교재 pg.177에서 풀어보면 된다.

5.7 숫자형 스트림

int calories = menu.stream()
				   .map(Dish::getCalories)
                   .reduce(0, Integer::sum);
  • 사실 위 코드에는 박싱 비용이 숨어 있다.
  • 내부적으로 합계를 계산하기 전에 Integer를 기본형으로 언박싱해야 한다.

➜ 스트림 API 숫자 스트림을 효율적으로 처리할 수 있도록 기본 특화 스트림을 제공한다.

1. 기본형 특화 스트림

  • 자바 8에서는IntStream, DoubleStream, LongStream 세 가지 기본형 특화 스트림을 제공한다.
  • 숫자 합계를 계산하는 sum, 최댓값 요소를 검색하는 max 등을 제공한다.
  • 필요시 다시 객체 스트림으로 복원하는 기능도 제공한다.
  • 특화 스트림은 오직 박싱 과정에서 일어나는 효율성과 관련 있으며 스트림에 추가 기능을 제공하지는 않는다는 사실을 기억하자

숫자 스트림으로 매핑

  • 스트림을 특화 스트림으로 변환할 때는 mapToInt, mapToDouble, mapToLong 세가지 메서드를 가장 많이 사용한다.
int calories = menu.stream()
				   .mapToInt(Dish::getCalories) // Stream<Dish> 반환
                   .sum(); // IntStream() 반환

객체 스트림으로 복원하기

IntStream intStream = menu.stream().mapToInt(Dish::getCalories); // 스트림 -> 숫자스트림
Stream<Integer> stream = intStream.boxed(); // 숫자스트림 -> 스트림

기본값: OptionalInt

OptionalInt maxCalories = menu.stream()
						      .mapToInt(Dish::getCalories)
                              .max();

2. 숫자 범위

프로그램에서는 특정 범위의 숫자를 이용해야 하는 상황이 자주 발생한다.

  • IntStreamLongStream에서는 rangerangeClosed라는 두 가지 정적 메서드를 제공한다.
IntStream evenNumbers = IntStream.rangeClosed(1, 100)  // [1, 100]의 범위를 나타냄
								 .filter(n -> n % 2 == 0); // 1 ~ 100까지의 짝수 스트림
  • IntStream.range(1, 100)은 1과 100 포함하지 않는다.

5.8 스트림 만들기

1. 값으로 스트림 만들기

🎈 Stream.of

Stream<String> stream = Stream.of("Modern", "Java", "In", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

🎈 Stream.empty

Stream<String> emptyStream = Stream.empty();

2. null이 될 수 있는 객체로 스트림 만들기 (자바 9)

Stream<String> homeValueStream = Stream.ofNullable(System.getProperty("home"));
  • System.getProperty는 제공된 키에 대응하는 속성이 없으면 null을 반환한다.

3. 배열로 스트림 만들기

🎈 Arrays.stream

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
  • Arrays.stream을 이용하여 기본형 int로 이루어진 배열을 IntStream으로 변환할 수 있다.

4. 파일로 스트림 만들기

생략,,,

5. 함수로 무한 스트림 만들기

  • 고정된 컬렉션에서 고정된 크기로 스트림을 만들었던 것과는 달리 크기가 고정되지 않은 스트림(무한 스트림)을 만들 수 있다.
  • iterategenerate에서 만든 스트림은 요청할 때마다 주어진 함수를 이용해서 값을 만든다.
  • 무제한으로 값을 계산할 수 있지만 보통 무한한 값을 출력하지 않도록 limit(n) 함수를 함께 연결해서 사용한다.

🎈 Stream.iterate

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream.iterate(0, n -> n + 2)
      .limit(10)
      .forEach(System.out::println);
  • iterate 메서드는 초깃값과 람다를 인수로 받아서 새로운 값을 끊임없이 생산할 수 있다.

🎈 Stream.generate

public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);
  • iterate와 달리 generate는 생산된 각 값을 연속적으로 계산하지 않는다.

0개의 댓글