오늘은 JAVA8에서 유용하게 사용되는 stream에 대해 좀 더 알아보고자 한다.
기본적으로 Stream은 데이터를 담고있는 저장소가 아니라는 것을 짚고 넘어가야 한다.
데이터를 담고있는 저장소라 함은 List, Map, Array 등의 Object를 의미한다.
하지만, Stream은 이들처럼 무언가를 담기위한 저장소가 아니라 데이터 처리를 위해 사용되는 파이프 라인이라 할 수 있다.
데이터 소스를 변경하지 않는다는게 무슨 말일까.
코드를 통해 알아보자.
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
List<String> list2 = list.stream().map(c -> c.toUpperCase())
.collect(Collectors.toList());
list.forEach(System.out::println); // a,b,c,d
list2.forEach(System.out::println); // A,B,C,D
우리는 map()을 통해 list의 요소 값을 대문자로 변환하는 작업을 진행했다.
하지만 이는 list2에 담길 뿐, 기존 list의 값을 변경하지 않는다는 것이 위에 언급한 데이터 소스를 변경하지 않는다의 의미라 볼 수 있다.
중개 오퍼레이션의 종류는 다음과 같다.
filter, map, limit, skip, sorted, ...
종료(=터미널) 오퍼레이션 종류는 다음과 같다.
collect, allMatch, forEach, ...
그럼 lazy하다는 것은 무엇을 의미할까. 코드를 통해 확인해보도록 하자.
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.stream()
.map(s -> {
System.out.println(s.toUpperCase());
return s.toUpperCase();
});
list.forEach(System.out::println); // a,b,c,d
이를 실행해보면 최하단의 sout만 실행되는 것을 확인할 수 있을 것이다.
이유는 위에 언급한 것과 같다.
map()은 중개 오퍼레이션으로 종료 오퍼레이션을 만나기 전까진 실행되지 않는다.
그런데 종료 오퍼레이션이 존재하지 않기 때문에 아예 실행되지 않은 것이다.
방대한 데이터를 처리할 때, 자동적으로 여러 thread에 분할하여 처리하도록 도와주는 function이 존재한다.
바로 parallelStream()이다.
사용하는 방법은 기존에 stream()으로 기재하던 부분을 parallelStream()으로 바꿔주면 된다.
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.ParallerStream()
.map(s -> {
System.out.println(Thread.currentThread().getName());
return s.toUpperCase();
})
.collect(Collectors.toList());
위와 같이 처리하면 기존에 stream()을 사용할 시, Main thread에서만 처리하던 것을 모두 다른 thread에서 처리되고 있는 것을 확인할 수 있을 것이다.
하지만 막무가내로 병렬처리하는 것은 좋지 않다.
병렬 처리를 하기 위해선 thread를 생성하는 비용, switching하는 비용 등이 존재하므로 오히려 독이 될 수 있기 때문이다.
그렇기 때문에 방대한 양을 처리할 경우에 한해 스스로 판단해 사용하는 것이 효과적이라 볼 수 있다.