[새싹] 모던 자바 인 액션 Chpater05-(2)

채상엽·2022년 5월 10일
0

업로드중..

layout: post
title: "모던 자바 인 액션 스터디 - chapter5-(2)"
date: 2022-03-25T00:00:00-00:00
author: sangyeop
categories: Sproutt-2nd


새싹 개발 서적 스터디 - 모던 자바 인 액션 Chapter5-(2)

숫자형 스트림

이전에 reduce() 메서드로 스트림 요소의 합을 구하는 예제를 살펴봤다

int calories = menu.stream()
  .map(Dish:Calories)
  .reduce(0, Integer::sum); // Integer를 박싱

기본형 특화 스트림

위 코드에는 Integer를 박싱하는 비용이 포함되어 있는데, 기본형 특화 스트림을 이용하면 다음과 같이 바꿔줄 수 있다.

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

mapToInt() 메서드의 경우에는 IntStream을 반환하기 때문에, IntSteam 인터페이스에서 제공하는 sum() 메서드를 사용할 수 있다.

객체 스트림으로 복원하기

그렇다면 숫자형 스트림을 다시 특화되지 않은 스트림으로 복원하려면 어떻게 해야할까? boxed()메서드를 이용해서 특화 스트림을 일반 스트림으로 변경할 수 있다.

IntStream intStream = menu.stream().mapToInt(Dish::getCalories); // 스트림을 숫자 스트림으로 변환
Stream<Integer> stream = intStream.boxed(); // 숫자 스트림을 일반 스트림으로 변환

기본값 : OptionalInt

이전에 값이 존재하는지 여부를 가리킬 수 있는 컨테이너 클래스 Optional이 등장했었다. OptionalInt를 이용해서 최대 값이 없을 경우에 사용할 기본값을 명시적으로 정의할 수 있다

int max = maxCalories.orElse(1);

숫자 범위

IntStream evenNumbers = IntStream.rangeClosed(1, 100) // [1, 100] 의 범위 지정
  .filter(n -> n % 2 == 0); // 짝수만 필터링
System.out.println(evenNumbers.count()); // 50개의 짝수가 있다

숫자 스트림 활용 : 피타고라스 수

  • 피타고라스 수

    a * a + b * b = c * c 를 만족하는 세 개의 정수 (a, b, c)가 존재

  • 세 수 표현하기

    int numbers = new int[]{3, 4, 5};
  • 좋은 필터링 조합

    a * a + b * b의 제곱근이 정수인지 확인할 수 있는 방법

    Math.sqrt(a*a + b*b) % 1 == 0;

    a라는 값이 주어지고 b는 스트림으로 제공된다고 가정 했을 때, 이를 filter() 메서드에 활용하면 다음과 같다

    filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
  • 집합 생성

    필터를 이용해 좋은 조합을 갖는 a, b를 선택할 수 있게 되었다. 마지막 세 번째 수를 찾아야 하는데, map()을 이용해서 각 요소를 피타고라스 수로 변환이 가능하다.

    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값 생성

    다음처럼 1부터 100까지 b를 생성한다.

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

    이 코드를 mapToObj를 이용해서 boxed()map() 과정을 하나로 합쳐줄 수 있다.

    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)})
               );

    flatMap()의 역할 :

    • a에서 사용할 1부터 100까지의 숫자를 만들었다.

    • 주어진 a를 세 수의 스트림으로 만든다

    • a를 매핑해서 만들어진 스트림의 스트림을 하나의 평준화된 스트림으로 만들어준다.

    flatMap()map()의 차이 : flatMap()은 스트림의 스트림을 반환하는 한편 map()은 스트림의 단일 요소를 반환한다. 그래서 스트림의 내부 요소가 또 다른 배열일 경우 flatMap()을 활용하면 유용하게 사용할 수 있다.

    개선할 점

    현재 코드에서는 제곱근을 두 번 계산한다. 그래서 (a*a, b*b, a*a + b*b) 형식을 만족하는 세 수를 만든 다음 결과만 필터링 하는 것이 더 효율적이다.

    Stream<double[]> pythagoreanTriples2 = IntStream.rangeClosed(1, 100)
      .boxed()
      .flatMap(a -> IntStream.rangeClosed(a, 100))
      .mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}) // 만들어진 세 수
      .filter(t -> t[2] % 1 == 0)); // 세 수의 세 번째 요소는 반드시 정수

스트림 만들기

값으로 스트림 만들기

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

그리고 empty() 메서드를 이용해서 빈 스트림을 만들어 줄 수도 있다.

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

null이 될 수 있는 객체로 스트림 만들기

System.getProperty()는 제공된 키와 일치하는 속성이 없으면 null을 반환한다. 이런 메서드를 스트림에 활용하려면 다음처럼 null을 명시적으로 확인해야 한다.

String homeValue = System.getProperty("home");
Stream<String> homeValueStream = homeValue == null ? Stream.empty() : Stream.of(value);

이를 Stream.ofNullable()을 이용하면 다음과 같이 바꿔줄 수 있다

Stream<String> values = Stream.of("config", "home", "user")
  .flatMap(key -> Stream.ofNullable(System.getProperty("key")));

배열로 스트림 만들기

배열을 인수로 받는 Arrays.stream()을 이용해서 스트림을 만들 수 있다

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); // IntStream의 sum() 사용 -> 합계 = 41

파일로 스트림 만들기

Files.lines()는 주어진 파일의 행 스트림을 문자열로 반환한다. 다음은 고유한 단어의 수를 찾는 프로그램이다

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) {
  // 여는중 에러 발생시 실행
}

각 행의 단어를 여러 스트림으로 만드는 것이 아니라, flatMap()을 이용하여 하나의 스트림으로 평면화 했다.

Stream은 자원을 자동으로 해제할 수 있는 AutoCloseable 이므로 try-finally가 필요 없다.

함수로 무한 스트림 만들기

Stream.iterate()Stream.generate()를 이용해서 무한 스트림(크기가 고정되지 않은 스트림)을 만들 수 있다. 보통 limit()와 함께 사용한다.

  • iterate 메서드
Stream.iterate(0, n -> n + 2) // 이전 결과에 2를 더한 값을 반환, 즉 여기서는 짝수만 반환
  .limit(10)
  .forEach(System.out::println);

일반적으로 연속된 일련의 값을 만들 때 iterate()를 사용한다.

두 번째 인수로 Predicate를 받아서 언제까지 작업을 수행할지 결정할 수도 있다

IntStream.iterate(0, n -> n < 100, n -> n + 4)
  .forEach(System.out::println);

이를 filter()를 이용해서 구현할 수 있겠다고 생각할수도 있지만, 이 경우에는 같은 결과를 얻을 수 없다. filter() 메서드는 언제 이 작업을 중단해야하는지 알지 못하기 때문이다.

두 번째 인자를 사용하지 않고 중단 시점을 선언하기 위해서는 takeWhile()이 해답이 될 수 있다.

IntStream.iterate(0, n -> n + 4)
  .takeWhile(n -> n < 100)
  .forEach(System.out::println);
  • generate 메서드

iterate()와 마찬가지로 값을 계산하는 무한 스트림을 만들 수 있다. 그러나 iterate()와는 달리 generate()는 생산된 각 값을 연속적으로 계산하지 않는다. Supplier<T>를 인수로 받아서 새로운 값을 생성한다.

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

limit()가 없다면 언바운드 상태가 된다. 또한 병렬 코드에서는 발행자에 상태가 있으면 안전하지 않다. 지금까지 배운 람다는 상태를 바꾸지 않지만, 발행자에서 익명클래스를 호출하면 상태필드를 커스터마이즈 할 수 있기 때문에 부작용이 생길 수 있다. 즉 iterate()불변 상태를 유지하지만 generate()가변 상태를 유지한다.

스트림을 병렬로 처리하면서 올바른 결과를 얻으려면 불변 상태 기법을 고수해야 한다. (7장에서 설명할 예정)

profile
프로게이머 연습생 출신 주니어 서버 개발자 채상엽입니다.

0개의 댓글