Contents
Java 8 (숫자 스트림)
   Sep 28, 2022     4 min read

숫자 스트림

➡️ 메뉴의 칼로리 합계

int calories = menu.stream()
     .map(Dish::getCalories)
     .reduce(0, Integer::sum);

System.out.println(calories);// 결과 : 4300

위 코드에는 박싱 비용이 숨어있다. 내부적으로 합계를 계산하기 전에 Integer를 기본으로 언박싱을해야 한다.

아래와 같이 직접 sum메서드를 호출할 수 있다면 더 좋지 않을까 ?

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

위 코드처럼 sum 메소드를 직접 호출할 수 없다.

이유는 map 메서드가 Stream를 생성하기 때문이다.

스트림의 요소 형식은 integer지만 인터페이스에는 sum 메서드가 없다.

  • 왜 sum 메서드가 없을까?

예를 들어 menu처럼 Stream 형식의 요소만 있다면 sum이라는 연산을 수행할 수 없기 때문이다.

  • 다른 방법이 있을까?

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

기본형 특화 스트림이란 무엇인가 ?

➡️ 자바 8에서는 세 가지 기본형 특화 스트림을 제공한다.

  1. int 요소에 특화된 IntStream을 제공한다.
  2. double 요소에 특화된 DoubleStream 을 제공한다.
  3. lon 요소에 특화된 LongStream 을 제공한다.

위와같이 특화된 스트림 제공으로 스트림 API는 박싱 비용을 지출하지 않아도 된다.

➡️ 각각의 인터페이스 자주 사용하는 숫자 관련 리듀싱 연산 수행 메서드를 제공한다.

  • 숫자 스트림의 합계를 계산하는 sum
  • 최댓값 요소를 검색하는 max
  • ..등등

➡️ 다시 객체 스트림으로 복원하는 기능도 제공한다.

**※기억하자 !

-** 특화 스트림은 오직 박싱 과정에서 일어나는 효율성과 관련 있으며 스트림에 추가 기능을 제공하지는 않는다는 사실을 기억하자

숫자 스트림으로 매핑

특화 스트림으로 변환할 때, 사용하는 메서드들

  • mapToInt
  • mapToDouble
  • mapToLong

위의 메서드들은 map과 같은 기능을 수행하지만, Stream 대신 특화된 스트림을 반환한다.

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

mapToInt 메서드는 각 요리에서 모든 칼로리(Integer 형식)를 추출한 다음에 IntStream을 반환한다.

주의할 점 : IntStream은 Stream을 반환을 하는것이 아니다 착각하면 안된다.

IntStream 인터페이스에서 제공하는 sum 메소드를 이용해서 칼로리 합계를 구할 수 있다.

  • 스트림이 비어있으면 기본값 0을 반환

➡️ IntStream이 지원하는 메서드들은 max, min, average ….등 유틸리티 메소드도 지원을 한다.


객체 스트림으로 복원하기

  • 숫자 스트림을 만든 다음에, 원상태인 특화되지 않은 스트림으로 복원하는 방법

IntStream 은 기본형의 정수값만 만들 수 있다.

IntStream 의 map 연산은 int를 인수로 받아서 int를 반환하는 람다(intUnaryOperator)를 인수로 받는다.

하지만 정수가 아닌 Dish같은 다른 값을 반환하고 싶으면 어떻게 해야할까 ?

  • 스트림 인터페이스에 정의된 일반적인 연산을 사용
  • boxed() 사용 : 특화 스트림 → 일반 스트림 변환
IntStream intStream = menu.stream()
     .mapToInt(Dish::getCalories); // <-- 스트림을 숫자 스트림으로 변환

Stream<Integer> stream = intStream.boxed(); // <-- 숫자 스트림을 스트림으로 변환

기본값 : OptionalInt

합계를 구할 때 0이라는 기본값이 있었기때문에 값을 구하는데 에러는 나오지 않았다.

하지만 .. IntStream 에서 최댓값을 찾을 때는 0이라는 기본값 때문에 잘못된 결과가 도출될 수 있다.

아래와 같은 상황일 때 어떻게 구별할까 ?

  • 스트림에 요소가 없는 상황
  • 실제 최댓값이 0인 상황

➡️Optional을 사용하자.

  1. Optional은 Integer, String 등의 참조 형식으로 파리미터화할 수 있다.
  2. OptionalInt, OptionalDouble, OptionalLong 세 가지 기본형 특화 스트림 버전도 제공
  • 예제) OptionalInt를 사용해서 IntStream의 최댓값 요소 구하기
OptionalInt maxCalories = menu.stream()
     .mapToInt(Dish::getCalories)
     .max();

짜잔✨✨

OptionalInt를 사용해서 최댓값이 없는 상황에 사용할 기본값을 명시적으로 정의할 수 있다.

int max = maxCalories.orElse(1); //<-- 값이 없을 때 기본 최댓값을 명시적으로 설정

숫자의 범위

IntStreamLongStream 에서는 range와 rangeClosed라는 두 가지 정적 메소드를 제공한다.

range()는 시작값과 종료값을 포함하지 않는다.

  • IntStream.range(시작값, 종료값)
  • LongStream.range(시작값, 종료값)

rangeClosed()는 시작값과 종료값을 포함한다.

  • IntStream.rangeClosed(시작값, 종료값)
  • LongStream.rangeClosed(시작값, 종료값)

예제) rangeClosed()

IntStream evenNumbers = IntStream.rangeClosed(1, 100) //<-- [1, 100]의 범위를 나타낸다.
     .filter(n -> n % 2 == 0);

System.out.println(evenNumbers.count()); // <-- 1부터 100까지 에는 50짝수가 있음

결과 값 : 50

예제) range

IntStream evenNumbers = IntStream.range(1, 100) // <-- (1, 100)의 범위를 나타낸다.
     .filter(n -> n % 2 == 0);

System.out.println(evenNumbers.count()); // <-- 49

결과 값 : 49

1과 100을 포함하지 않기 때문에 49개가 나온다.