스트림(Stream)이란?
스트림은 '데이터의 흐름'입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 이전에 배웠던 람다를 이용하여 코드의 양을 줄여 간결하게 표현할 수 있습니다.
즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.
또한 간단하게 병렬처리(multi-threading)가 가능하다는 장점이 있습니다.
이 말은 즉, 스레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.
스트림에 대한 내용은 크게 3가지로 나눌 수 있습니다.
- 생성
- 중간연산
- 최종연산
이번 글은 이 세 가지 내용 중 생성에 대한 내용을 다뤄보고자 합니다.
생성하기
스트림을 이용하기 위해서는 먼저 생성을 하여야 합니다.
보통 스트림은 배열과 컬렉션을 이용해서 만들지만 이 외에도 다양한 방법으로 스트림을 만들 수 있습니다.
예제들과 함께 살펴보도록 하겠습니다.
배열 스트림
배열을 이용하여 스트림을 만드는 방법은 Arrays.stream 메서드를 이용하여 만들 수 있습니다. 아래의 코드 예제와 함께 보도록 하겠습니다.
String[] arr = new String[]{"i","am","cool"};
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = (arr, 1, 3); // ["am","cool"]
컬렉션 스트림
컬렉션 타입(Collection, List, Set)은 인터페이스에 추가된 default 메서드 stream을 이용해 스트림을 만들 수 있습니다.
public interface Collection<E> extends Iterable<E> {
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
//...
}
아래 예제 코드와 같이 생성할 수 있습니다.
List<String> list = Arrays.asList("i","am","nom");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
비어 있는 스트림
비어 있는 스트림(empty stream)도 생성할 수 있습니다. 빈 스트림은 요소가 없을 때 null 대신 사용할 수 있습니다.
public Stream<String> streamOf(List<String> list) {
return list == null || list.isEmpry()
? Stream.empty()
: list.stream();
}
Stream.builder()
빌더(Builder)를 사용하면 스트림에 직접적으로 원하는 값을 넣을 수 있습니다. 마지막에 build 메서드로 스트림을 리턴합니다
Stream<String> builderStream =
Stream.<String>builder()
.add("i").add("am").add("nom")
.builder(); // [i,am,nom]
Stream.generate()
generate 메서드를 이용하면 Supplier <T>에 해당하는 람다로 값을 넣을 수 있습니다. Supplier <T>는 인자는 없고 리턴값만 있는 함수형 인터페이스입니다.
public static<T> Stream<T> generate(Supplier<T> s) {...}
이때 생성되는 스트림은 크기가 정해져 있지 않기에 특정 사이즈로 최대 크기를 제한해야 합니다.
Stream<String> generatedStream =
Stream.generate(() -> "gen").limit(5); // [gen, gen, gen, gen, gen]
Stream.iterate()
iterate 메서드를 이용해 초기값과 해당 값을 다루는 람다를 이용해 스트림에 들어갈 요소를 만듭니다.
다음 예제에서는 30이 초기값이고 값이 2씩 증가하는 값들이 들어가게 됩니다.
즉 요소가 다음 요소의 인풋으로 들어갑니다. 이 방법도 스트림의 사이즈가 무한하기 때문에 특정 사이즈로 제한하여야 합니다.
Stream<Integer> iteratedStream =
Stream.iterate(30, n -> n + 2).limit(5); // [30, 32, 34, 36, 38]
기본 타입형 스트림
제네릭을 사용하여 기본 타입 스트림을 생성할 수 있지만 제네릭을 사용하지 않고 직접적으로 해당 타입의 스트림을 다룰 수도 있습니다. range와 rangeClosed는 범위의 차이입니다. 두 번째 인자인 종료 지점이 포함 유무를 나타냅니다.
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeCloased(1, 5); // [1, 2, 3, 4, 5]
제네릭을 사용하지 않았기 때문에 불필요한 오토박싱(auto-boxing)이 일어나지 않습니다. 필요한 경우 boxed 메서드를 이용해서 박싱(boxing)을 할 수 있습니다.
Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();
Random 클래스를 이용해 세 가지 타입(IntStream, LongStream, DoubleStream)의 스트림을 만들 수 있습니다.
쉽게 난수 스트림을 생성해서 여러 가지 후속 작업을 취할 수 있습니다.
DoubleStream doubles = new Random().doubles(3); // 난수 3개 생성
문자열 스트림
String을 이용해 스트림을 생성할 수 있습니다. 다음 코드 예제는 스트링의 각 문자(char)를 IntStream으로 변환한 예제입니다. char는 문자이지만 본질적으로는 숫자이기 때문에 가능합니다.
IntStream charsStream = "Stream".chars(); // [88, 116, 114, 101, 97, 109]
다음은 정규식(RegEx)을 이용하여 문자열을 자르고, 각 요소들로 스트림을 만든 예제입니다.
Stream<String> stringStream =
Pattern.compile(", ").splitAsStream("Nom, is, Man");
// [Nom, is, Man]
파일 스트림
자바 NIO의 Files 클래스의 lines 메서드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어줍니다.
Stream<String> lineStream =
Files.lines(Paths.get("file.txt"),
Charset.forName("UTF-8"));
병렬 스트림
스트림 생성 시에 stream 대신 parallelStream 메서드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다. 내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join framework을 사용합니다
// 병렬 스트림 생성
Stream<Product> parallelStream = productList.parallelStream();
// 병렬 여부 확인
boolean is parallel = parallelStream.isParallel();
따라서 다음 예제 코드는 각 작업을 쓰레드를 이용해 병렬 처리합니다.
boolean isMany = parallelStream
.map(product -> product.getAmount() * 10)
.anyMatch(amount -> amount > 200);
다음은 배열을 이용해서 병렬 스트림을 생성하는 경우입니다.
Arrays.stream(arr).parallel();
컬렉션과 배열이 아닌 경우는 다음과 같이 parallel 메서드를 이용해서 처리합니다.
IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();
다시 시퀀셜(sequential) 모드로 돌리고 싶다면 다음처럼 sequential 메서드를 사용합니다. 뒤에서 한번 더 다루겠지만 반드시 병렬 스트림이 좋은 것은 아닙니다.
IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();
스트림 연결하기
Stream.concat 메서드를 이용해 두 개의 스트림을 연결해서 새로운 스트림을 만들어낼 수 있습니다.
Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]
이번 글을 통해 스트림의 세 처리 과정 중 생성에 대해서 공부하여 보았습니다.
다음 글을 통해 스트림의 세 처리 과정 중 중간 연산에 대해 공부하도록 하겠습니다
Reference
https://futurecreator.github.io/2018/08/26/java-8-streams/
'JAVA' 카테고리의 다른 글
[JAVA] 스트림 (Stream) - 최종 연산 (0) | 2023.03.19 |
---|---|
[JAVA] 스트림 (Stream) - 중간 연산 (0) | 2023.03.18 |
[JAVA] 람다 (Lambda) (0) | 2023.03.16 |
[JAVA] 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy) (0) | 2023.03.15 |
[JAVA] 싱글턴 패턴(Singleton pattern) (0) | 2023.03.14 |