[Java8 in Action] 5장. 스트림 활용

스트림 활용

여기서는 기본적인 filter, map 등의 기본연산은 생략하고, 조금 더 실용적인 스트림 활용만을 정리해보자.

List<Integer> num1 = Arrays.asList(1,2,3);
List<Integer> num2 = Arrays.asList(3,4);

List<int[]> pairs = num1.stream()
  											.flatMap(i -> num2.stream()
                                					.map(j -> new int[]{i,j}))
  											.collect(toList());

검색과 매칭

allMatch, anyMatch, noneMatch, findFirst, findAny

findFirst, findAny는 언제 사용할까?

병렬 실행에서는 첫 번째 요소를 찾기 어렵다. 따라서 요소의 반환 순서가 상관없다면 병렬 스트림에서는 제약이 적은 findAny를 사용.

Reducing

reduce 메서드의 장점과 병렬화

기존의 단계적 반복으로 합계를 구하는 것과 reduce를 이용해서 합계를 구하는 것은 어떤 차이가 있을까? reduce를 이용하면 내부 반복이 추상화되면서 내부 구현에서 병렬로 reduce를 실행 할 수 있게 된다. 반복적인 합계에서는 sum 변수를 공유해야 하므로 쉽게 병렬화하기 어렵다.

강제적으로 동기화시킨다 하더라도 결국 병렬화로 얻어야 할 이득이 스레드 간의 소모적인 경쟁 때문에 상쇄되어 버린따는 사실을 알게 될 것이다! 사실 이 작업을 병렬화하려면 입력을 분할하고, 분할된 입력을 더한 다음에, 더한 값을 합쳐야 한다. 지금 까지 살펴본 코드와는 조금 다른 코드가 나타난다.

image-20190810115751380

숫자형 스트림.

앞에서 언급한 것 중에 하나가 unboxedboxed에 대해서 언급한 바 있다.

박싱하는 것도 비용이 많이 발생하기 때문에 IntStream, LongStream등의 여러 스트림이 있다고 언급했었는데, 여기서 그 부분에 대해서 조금 더 자세히 다뤄보자.

int calories = menu.stream()
  								 .map(Dish::getCalories)
  								 .sum();

위 코드에서는 박싱이 자체적으로 이루어져 비용이 발생할 것.

이럴 때 할 수 있는 스트림으로 기본형 특화 스트림이 존재한다.

int calories = menu.stream() //Stream<Dish> 반환
  								 .mapToInt(Dish::getCalories) //IntStream 반환
  								 .sum();

이번에는 객체 스트림으로 복원

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

피타고라스 수를 만들면서 해당 숫자형 스트림을 활용해보자.

피타고라스 이론 -

a * a + b * b = c * c

예를 들어 (3,4,5) => new int[]{3, 4, 5} 로 표현.

두 수가 피타고라스 수의 일부가 될 수 있는 좋은 조합인지 어떻게 확인할 수 있을까?

a * a + b * b 의 제곱근이 정수인지 확인할 수 있다. => '표현식 % 1'의 결과가 0, 즉 소수점이 없어야 한다.

즉, filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)

집합 생성

stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
  .map(b -> new int[] {a, b, (int) Math.sqrt(a * a + b * b)})

b값 생성

IntStream.rangeClosed(1, 100)
  			 .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
  			 .boxed()
  			 .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

여기서 IntStream 을 쓰면

IntStream.rangeClosed(1, 100)
  			 .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
  			 .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

a값 생성

Stream<int[]> pythagoreanTriples =
  	IntStream.rangeClosed(1, 100).boxed()
  					 .flatMap(a ->
                     	IntStream.rangeClosed(a, 100)
                     					 .filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
                     .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})
                     );

코드 실행

이제 코드 구현은 완료되었고 limit 를 이용해서 얼마나 많은 세 수를 포함하는 스트림을 만들 것인지만 결정하면 된다.


스트림 만들기

값으로 스트림 만들기

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

다음처럼 empty 메서드를 이용해서 스트림을 비울 수 있다.

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

배열로 스트림 만들기

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); // 41

파일로 스트림 만들기

long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
  uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
    								 .distinct()
    								 .count();
}
catch( IOException e ){
  
}

함수로 무한 스트림 만들기

무한 스트림을 만들 수 있는 2가지 방법 Stream.iterate , Stream.generate 제공.

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

예제.

// 피보나치수열 집합 만들기
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...

Stream.iterate(new int[]{0, 1}, ???)
  		.limit(20)
  		.forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));

---

Stream.iterate(new int []{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
  		.limit(10)
  		.map(t -> t[0])
  		.forEach(System.out::println)

generate

Stream.generate(Math::random)
			.limit(5)
			.forEach(System.out::println)

Generate 를 쓰는 이유는?

우리가 사용한 공급자 supplier 는 상태가 없는 메서드, 즉 나중에 계산에 사용할 어떤 값도 저장해두지 않는다. 하지만 공급자에 꼭 상태가 없어야 하는 것은 아니다.

요약