Streams
자바 8에서 추가한 스트림은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for문 혹은 foreach문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었습니다. 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가발생합니다.
스트림은 '데이터의 흐름'입니다. 배열 또는 컬렉션 인스턴스 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다.
또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.
또 하나의 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 점입니다. 즉, 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.
스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.
- 생성하기 : 스트림 인스턴스 생성
- 가공하기 : 필터링 및 매핑등 원하는 결과를 만들어가는 중간 작업 (intermediate operations)
- 결과 만들기 : 최종적으로 결과를 만들어내는 작업 (termianl operations)
전체 ➡️ 맵핑 ➡️ 필터링1 ➡️필터링2 ➡️ 결과 만들기 ➡️ 결과물
이번 포스팅에서는 생성하기에 대한 내용을 다뤄보도록 하겠습니당 :-)
생성하기
배열 스트림
스트림을 이용하기 위해서는 먼저 생성을 해야 합니다. 스트림은 배열 또는 컬렉션 인스턴스를 이용해서 생성할 수 있습니다.
배열은 다음과 같이 arays.streams메소드를 사용합니다
String[] arr = new String[]("a","b","c");
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr,1,3); // 1~2요소 [b,c]
컬렉션 스트림
컬렉션 타입 (Collection,List,Set) 의 경우 인터페이스에 추가된 디폴트 메서드 stream을 이용해서 스트림을 만들 수 있습니다.
public interface Collection<E> extends Iterable<E> {
default Stream<E> stream(){
return StreamSupport.stream(spliterator(),false);
}
}
그러면 다음과 같이 생성할 수 있습니다.
List<String> list = Array.asList("a","b","c");
Stream<String> stream = list.stream();
Stream<String> parallerStream = list.parallerStream(); // 병렬 처리 스트림
비어 있는 스트림
비어 있는 스트림(empty streams)도 생성할 수 있습니다. 언제 빈 스트림이 필요할까요?
빈 스트림은 요소가 없을 때 null대신 사용할 수 있습니다.
public Stream<String> streamOf(List<String> list) {
return list == null || list.isEmpty()? Stream.empty() : list.stream();
}
Stream.builder()
빌더를 사용하면 스트림에 직접적으로 원하는 값을 넣을 수 있습니다. 마지막에 build 메서드로 스트림을 리턴합니다.
Stream<String> builderStream =
Stream.<String>bulder()
.add("kim")
.add("ahreum")
.add("developer")
.build();
Stream.generate()
generate메서드를 이용하면 Supplier에 해당하는 람다로 값을 넣을 수 있습니다.Supplier는 인자는 없고 리턴값만 있는 함수형 인터페이스입니다.
람다에서 리턴하는 값이 들어갑니다.
public static Stream generate(Supplier s) {...}
이 때 생성되는 스트림은 크기가 정해져있지 않고 무한(infinite)하기 때문에 특정 사이즈로 최대 크기를 제한해야 합니다.java Stream generatedStream = Stream.generate(()->"gen").limit(5); //5개의 gen이 들어간 스트림이 생성 됨
Stream.iterate()
iterate메서드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어갈 요소를 만듭니다. 다음 예제에서는 30이 초기값이고 값이 2씩 증가하는 값들이 들어가게 됩니다. 즉, 요소가 다음의 요소의 인풋으로 들어갑니다. 이 방법도 스트림의 사이즈가 무한하기 때문에 특정 사이즈로 제한 해야 합니다.
Stream interatedStream = Stream.iterate(30,n->n+2).limit(5);
기본 타입형 스트림
물론 제네릭을 사용하면 리스트나 배열을 이용해서 기본 타입 스트림을 생성할 수 있습니다.
하지만 제네릭을 사용하지 않고 직접적으로 해당 타입의 스트림을 다룰 수도 있습니다.range와 rangeClosed는 범위의 차이입니다. 두 번째 인자인 종료지점이 포함되느냐 안되느냐의 차이 입니다.
IntStream intStream = IntStream.range(1,5) //1,2,3,4 LongStream longStream = LongStream.rangeClosed(1,5) //1.2,3,4,5
제네릭을 사용하지 않기 때문에 불필요한 오토박싱(auto-boxing)이 일어나지 않습니다. 필요한 경우 boxed메소드를 이용해서 박싱 할 수 있습니다.
Stream<Integer> boxedIntStream = IntStream.range(1,5).boxed();
Java 8의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream,LongStream,DoubleStream)을 만들어낼 수 있습니다. 쉽게 난수 스트림을 생성해서 여러가지 후속 작업을 취할수 있어서 유용합니다.
DoubleStream doubles = new Random().double(3); // 난수 3개 생성
문자열 스트링
스트링을 이용해서 스트림을 생성할수도 있습니다. 다음은 스트링의 각 문자(cha)를 IntStream으로 변환한 예제입니다.
char는 문자이지만 본질적으로 숫자이기 때문에 가능합니다.
IntStream charsStream = "Stream".chars();
다음은 정규표현식을 이용해서 문자열을 자르고, 각 요소들로 스트림을 만든 예제입니다.
Stream<String> stringStream = Pattern.compile(",").splitAsStream("a,b,c");
파일 스트림
자바 NIO 의 Files 클래스의 lines 메소드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어줍니다
Stream<String> lineStream =
Files.lines(Paths.get("file.txt"),
Charset.forName("UTF-8"));
병렬 스트림 (Paraller Stream)
스트림 생성 시 사용하는 stream 대신 parallerStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다.
내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join Framework를 사용합니다.
// 병렬 스트림
Stream<Product> parallelStream = productList.parallerStream();
// 병렬 여부 확인
boolean isParallel = 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);
출처 🙇🏻
'Dev > Java' 카테고리의 다른 글
| Functional Programming In Java (ft. Lambda , StreamAPI) (0) | 2023.06.18 |
|---|---|
| Java Streams (2) - 가공하기 ⚒ (0) | 2022.12.29 |
| [Java] JVM 메모리 구조 (0) | 2022.12.05 |
| [Java] Default Method in Java8 (0) | 2022.11.07 |
| [Java] Garbage Collection(가비지 컬렉션) 톺아보기 (1) | 2022.10.02 |