CHAPTER5 - 스트림 활용(2)

2025. 2. 17. 22:54Book/모던 자바 인 액션

목차.

1. 특정 범위의 숫자와 같은 숫자 스트림 사용하기

2. 다중 소스로부터 스트림 만들기

3. 무한 스트림

 

 

1. 숫자형 스트림

앞서 reduce 메서드로 스트림 요소으 ㅣ합을 구하는 예제를 살펴봤다.

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

 

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

위와 같은 상황에서는 효율적으로 처리할 수 있도록 기본형 특화 스트림을 사용할 수 있다.

 

1.1 기본형 특화 스트림

자바 8에서는 세 가지 기본형 특화 스트림을 제공한다. 스트림 API는 박싱 비용을 피할 수 있도록 IntStream, DoubleStream, LongStream을 제공한다. 각 인터페이스는 숫자 스트림의 합계를 계산하는 sum, 최댓값 요소를 검색하는 max 같이 자주 사용하는 숫자 관련 리듀싱 연산 수행 메서드를 제공한다.

 

숫자 스트림으로 매핑

스트림을 특화 스트림으로 변환할 때는 mapToInt, mapToDouble, mapToLong 세 가지 메서드를 가장 많이 사용한다. 이들 메서드는 map과 정확히 같은 기능을 수행하지만, Stream<T> 대신 특회된 스트림을 반환한다.

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

 

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

 

기본값 : OptionalInt

합계 에제에서는 0이라는 기본값이 있었다. 하지만 IntStream에서 최댓값을 찾을 때는 0이라는 기본값 때문에 잘못된 결과가 도출될 수 있다. 스트림에 요소가 없는 상황과 실제 최댓값이 0인 상황을 어떻게 구별할 수 있어야 한다. 이 경우 Optionl을 사용하면 된다.

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

int max = maxCalories.orElse(0);

 

1.2 숫자 범위

가끔 코드를 짤 때 특정 범위의 숫자를 이용해야 하는 상황이 발생할 수 있다. 예를 들어 1 ~ 100 사이의 숫자를 생성하려 한다고 가정하자. 자바 8의 IntStream과 LongStream에서는 range와 rangeClosed라는 두 가지 정적 메서드를 제공한다. 두 메서드 모두 첫 번째 인수로 시작값을, 두 번째 인수로 종료값을 갖는다. range 메서드는 시작값과 종료값이 결과에 포함되지 않는 반면 rangeClosedㅡㄴ 시작값과 종료값이 결과에 포함된다는 점이 다르다.

IntStream.rangeClosed(1, 100)
        .filter(i -> i % 2 == 0)
        .forEach(System.out::println);

 

위 코드는 1 ~ 100 까지 숫자 범위에서 짝수 50개를 출력하는 반면 range 메서드를 사용하면 1 과 100을 뺀 범위에서 49개의 짝수를 출력한다.

 

 

2. 스트림 만들기

 

2.1 값으로 스트림 만들기

임의의 수를 인수로 받는 정적 메서드 Stream.of()를 이용해서 스트림을 만들 수 있다.

Stream<String> stream = Stream.of("Modern", "Java", "In", "Action");

stream.map(String::toUpperCase).forEach(System.out::println);

->
MODERN
JAVA
IN
ACTION

 

위 코드는 스트림의 모든 문자열을 대문자로 변환한 후 문자열을 하나씩 출력한다.

 

2.2 배열로 스트림 만들기

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

 

 

2.3 파일로 스트림 만들기

파일을 처리하는 등의 I/O 연산에 사용하는 자바의 NIO API도 스트림 API를 활용할 수 있도록 업데이트되었다. 예를들어 Files.lines는 주어진 파일의 행 스트림을 문자열로 반환한다. 아래 예제는 파일에서 고유한 단어 수를 찾는 코드이다.

try(Stream<String> lines
        = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {

    long uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
                                                         .distinct()
                                                         .count();

} catch (IOException e) {
    //파일을 열다 발생한 오류
}

 

 

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

스트림 API는 함수에서 스트림을 만들 수 있는 두 정적 메서드 Stream.iterate, Stream.generate를 제공한다. 두 연산을 이용해서 무한 스림을 만들 수 있다. 하지만 보통 무한한 값을 출력하거나 연산하지 않도록 limit(n) 함수를 함께 연결해서 사용한다.

 

 

1. Iterate 메서드

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

 

iterate메서드는 초깃값과 람다를 인수로 받아서 새로운 값을 끊임없이 생산할 수 있다. 에제에서는 이전 결과에 2를 더한 값을 반환한다.

자바 9의 iterate 메소드는 프레디케이트를 지원한다. 예를 들어 0에서 시작해서 100보다 크면 숫자 생성을 중단하는 코드를 구현할 수 있다.

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

 

 

takeWhile을 이용하는 것도 같은 결과를 준다.

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

 

 

 

2. generate 메서드

 

iterate와 비슷하게 generate도 요구할 때 값을 계산하는 무한 스트림을 만들 수 있다. 하지만 interate와 다르게 generate는 생산된 각 값을 연속적으로 계산하지 않는다. generate는 Supplier<T>를 인수로 받아서 새로운 값을 생산한다.

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