[모던 자바] 스트림(Stream) 만들기
앞서 스트림이 무엇인지, 스트림을 어떻게 활용하는지 알아보았다.
이번엔 여러가지 스트림을 만드는 방법에 대해 알아보자.
값으로 스트림 만들기
임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해서 스트림을 만들 수 있다.
Stream<String> stream = Stream.of("a", "b", "c", "d", "e");
stream.map(String::toUpperCase).forEach(System.out::println);
다음 처럼 empty 메서드를 이용해서 스트림을 비울 수 있다.
Stream<String> emptyStream = Stream.empty();
null이 될 수 있는 객체로 스트림 만들기
java9에서는 null이 될 수 있는 개체를 스트림으로 만들 수 있는 새로운 메소드가 추가되었다.
때때로 null이 될 수 있는 객체를 스트림으로 만들어야 할 때가 있다.
ofNullable 메소드를 사용하면 값이 null 일때는 빈 스트림을 반환하고, null이 아닐때는 생성된 스트림으로 반환한다.
String value = null;
Stream<String> nullableStream = Stream.ofNullable(value);
// Stream<String> nullableStream = value == null ? Stream.empty() : Stream.of(value);
배열로 스트림 만들기
배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해서 스트림을 만들 수 있다.
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
파일로 스트림 만들기
파일을 처리하는 등의 I/O 연산에 사용되는 자바의 NOI API(비브록 I/O)도 스트림 API를 활용할 수 있도록 업데이트 되었다. java.nio.file.Files의 많은 정적 메서드가 스트림을 반환한다.
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) {
...
}
위 코드는 Files.lines라는 정적 메서드로 파일을 읽어 스트림으로 반환하고 있다.
반환된 문자열 스트림에 띄어쓰기(" ")로 배열화하고 flatMap메서드를 사용해 스트림으로 평면화 시킨 후 중복을 없앤 갯수를 반환하여 uniqueWords 변수에 저장했다. 이처럼 File을 스트림을 변환해 연산이 가능하다.
함수로 무한 스트림 만들기
스트림 API는 함수에서 스트림을 만들 수 있는 두 정적 메서드 Stream.iterate와 Stream.generate를 제공한다.
두 연산을 이용해서 무한 스트림(크기가 고정되지 않은)을 만들 수 있다.
iterate와 generate에서 만든 스트림은 요청할 때마다 주어진 함수를 이용해서 값을 만든다. 따라서 무제한은 값을 계산할 수 있다. 하지만 보통 무한한 값을 출력하지 않도록 limit(n) 함수를 함께 연결해서 사용한다.
iterate
iterate는 초깃값과 람다를 인수로 받아서 새로운 값을 끊임없이 생산할 수 있고, 초깃값은 스트림 첫번째 요소로 반환된다.
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
기본적으로 iterate는 기존 결과에 의존해서 순차적으로 연산을 수행하고, 요청할 때마다 값을 생산할 수 있으며 끝이 없으므로 무한 스트림을 만든다. 이러한 스트림을 언바운드 스트림(unbounded stream)이라고 표현한다.
generate
iterate와 비슷하게 generate도 요구할 때 값을 계싼하는 무한 스트림을 만들 수 있다.
하지만 iterate와 달리 생성된 각 값을 연속적으로 계산하지 않는다.
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
일반적으로 연속된 일련의 값을 만들 때는 iterate를 사용하고, 병렬 처리처럼 상태가 없어야 하는 상황에는 generate를 사용한다.
스트림을 병렬로 처리하면서 올바른 결과를 얻으려면 상태가 없어야 하고, 만약 있다면 변경되어서는 안된다.