이전 챕터에 이어서 스트림을 활용하는 방법에 대해 더욱 자세히 알아본다.
이전 장 chpt 03에서 메서드로 스트림 요소의 합을 구하는 예제를 살펴봤다. 예를 들어 다음처럼 메뉴의 칼로리 합계를 계산할 수 있다.
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
사실 위 코드엔 박싱비용이 숨어있다. 내부적으로 합계를 계산하기 전에 Integer를 기본형으로 언박싱해야한다.
다음 코드처럼 sum을 호출할 수 있다면 더 좋지 않을까 ?
int calories = menu.stream()
.map(Dish::getCalories)
.sum();
하지만 그럴 수 없다. map 메서드가 Stream<T>
를 생성하기 때문이다. 스트림의 요소 형식은 Integer지만 인터페이스에는 sum 메서드가 없다. 왜 sum 메서드가 없나, menu처럼 Stream<Dish>
형식의 요소만 있다면 sum이라는 연산을 수행할 수 없기 때문이다.
그렇기 때문에 스트림 API 숫자 스트림을 효율적으로 처리하기 위하여 기본 특화 스트림을 제공한다.
각각의 인터페이스는 max, sum과 같은 자주 사용하는 숫자관련 리듀싱 연산 수행 메서드를 제공한다. 또한 필요할 때 다시 객체 스트림으로 복원하는 기능도 제공한다.
특화 스트림은 오직 박싱과정의 효율성에만 관련이 있다. 스트림에 추가기능을 제공하는 것은 아니라는 사실을 기억하자.
스트림을 특화 스트림으로 변환 시킬땐 mapToInt
, mapToDouble
, mapToLong
을 가장 많이 사용한다. map과 정확히 같은 기능을 수행하지만 특화 스트트림을 반환한다.
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
IntStream을 반환받아 sum 메서드를 사용할 수 있다. 스트림이 비어있다면 0을 반환하고 IntStream은 max, min, average 등 다양한 유틸리티 메서드도 지원한다.
특화 스트림을 만든 뒤에 원 상태로 복원 할 수 있을까? 우리가 갖고 싶은게 숫자가 아닌 Dish 같은 다른 값을 받고 싶다면 ?
그럼 스트림 인터페이스에 정의된 일반적인 연산을 사용해야한다. 다음 예제처럼 boxed
메서드를 이용하여 다시 복원하자.
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
참고로 스트림에 요소가 없는 상황과 실제 최댓값이 0인 상황을 구별하려면 OptionalInt
같이 Optional을 사용하여 최댓값 요소를 찾을 수 있다.
특정 범위의 숫자를 이용해야 할 때 range
와 rangeClosed
메서드를 사용할 수 있다. 이는 IntStream
, LongStream
두 기본형 특화 스트림에서 지원된다. range
는 열린 구간을 의미하며, rangeClosed
는 닫힌 구간을 의미한다.
스트림이 데이터 처리 질의를 표현하는 강력한 도구임을 충분히 확인했다. 우리가 배웠던 방식 외에 다양한 방식으로 스트림을 만들 수 있다. 그 방법들에 대해서 배워보자.
정적 메서드 Stream.of을 이용하여 스트림을 만들 수 있다.
Stream<String> stream = Stream.of("jiny","choi","samuel");
stream.map(String::toUpperCase).forEach(System.out::println);
//스트림 비우기
Stream<String> emptyStream = Stream.empty();
자바 9부터 지원되며 Stream.ofNullable 메서드를 이용하여 null이 될 수 있는 객체를 지원하는 스트림을 만들 수 있다. null이 될 수 있는 객체를 스트림으로 만들어야 할 때도 있다. 예를 들면 System.getProperty
는 제공된 키에 대응하는 속성이 없으면 null을 반환한다. 그런 메소드를 스트림에 활용하려면 다음처럼 null을 명시적으로 확인했어야 했다.
하지만 Stream.ofNullable
을 활용하면 다음처럼 구현이 가능하다.
Stream<String> homeValueStream =
Stream.of("config", "home", "uesr")
.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
배열을 인수로 받는 정적 메서드 Arrays.stream을 이용하여 스트림을 만들 수 있다.
int [] numbers = {2,3,4,5,6,7};
int sum = Arrays.stream(numbers).sum();
파일을 처리하는 등의 I/O 연산에 사용하는 자바의 NIO API(비블록 I/O)도 스트림 API를 활용할 수 있도록 업데이트되었다. java.nio.file.Files의 많은 정적 메서드가 스트림을 반환한다. 예를 들어 Files.lines는 주어진 파일의 행 스트림을 문자열로 반환한다.
Stream.iterate와 Stream.generate를 통해 함수를 이용하여 무한 스트림을 만들 수 있다. iterate와 generate에서 만든 스트림은 요청할 때마다 주어진 함수를 이용해서 값을 만든다. 따라서 무제한으로 값을 계산할 수 있지만, 보통 무한한 값을 출력하지 않도록 limit(n) 함수를 함께 연결해서 사용한다.
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
`public static Stream generate(Supplier s)``
이 장은 내용이 길었지만, 중요한 개념들을 설명한 장이었다. 컬렉션을 더 효율적으로 처리할 수 있게 되었고, 스트림을 이용하면 복잡한 데이터 처리 질의를 간결하게 표현할 수 있다.