반응형

지난 포스팅(java.util.function 표준 API 파헤치기(feat.람다식))에 이어 계속해서 자바 8 버전에서 나온 변화들을 살펴보고 있다.

이번에 함께 정리해볼 것은 스트림(Stream)이다.

자바스크립트(JavaScript) 개발 경험이 있다면 반가운 forEach() 메소드 등도 만나볼 수 있다.

 

스트림(Stream)이란?

스트림은 배열, 리스트 등 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자로, 자바 8부터 추가되었다.

예시를 살펴보자.

List<String> names = Arrays.asList("Kim", "Lee", "Park");
Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));

눈여겨 볼 것은 List 인터페이스에 stream()이라는 메소드가 추가되었다는 것과 그 메소드가 반환하는 Stream 객체가 List와 같은 Generic 타입을 갖는다는 것이다.

그리고 list로부터 만들어진 stream을 이용하여 다음과 같은 forEach() 메소드를 실행한다.

 

void forEach(Consumer<T> action);

 

어라? 반가운 녀석이 있다. 바로 파라미터에 선언된 Consumer<T> 타입은 지난 포스팅에서 공부한 녀석이다.

지난 포스팅을 보고 오기 귀찮으신 분들을 위해 간단히 설명드리자면, Consumer는 파라미터는 있지만 리턴 값이 없는 람다식을 저장하는 인터페이스이다.

그런 의미에서 stream.forEach() 메소드는 컬렉션의 요소를 하나씩 방문하여 forEach의 파라미터로 넘겨준 action을 수행하는 메소드라고 보면 되겠다.

Stream에는 forEach() 외에도 filter(), map(), count(), sorted() 등 다양한 메소드를 제공하는데, 이에 대해 자세한 내용은 Java Docs을 참고하면 되겠다.

 

자바를 열심히 공부하신 분이라면, 여기까지 봤을 때 의문점이 들 것이다.

자바에는 이미 컬렉션의 요소에 대해 순차적으로 방문 해주는 Iterator가 있는데 왜 굳이 Stream을 만들었지?

 

지금부터 Stream이 Iterator에 비해 갖는 강점들과 특징에 대해 살펴보도록 하겠다.

 

스트림(Stream)의 특징

1. 요소에 대해 람다식으로 처리할 수 있다.

앞서 말한 것처럼 Stream에는 forEach() 외에도 다양한 메소드들이 있는데, 대부분의 메소드들이 파라미터로 함수적 인터페이스 타입을 요구한다.

그렇기 때문에 스트림을 사용하는 개발자는 스트림이 제공하는 메소드에 추가적으로 (파라미터+로직)을 넘겨줄 수 있다.

 

2. 내부 반복자(Internal Iterator)를 사용하기 때문에 병렬 처리가 쉽다.

내부 반복자? 처음 들어본다. 내부 반복자가 있다는 말은 외부 반복자라는 말도 있다는 것이니 외부 반복자를 먼저 알아보자면, 외부 반복자(External Iterator)는 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 것을 말한다.

즉, 우리가 그동안 자주 사용했던 index를 이용해 for문으로 방문하는 것이나 iterator를 이용해 while문으로 요소를 꺼내 썼던 것들이 바로 이 외부 반복자에 해당한다.

반면에 내부 반복자(Internal Iterator)는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 각 요소당 처리해야하는 코드만 제공하는 것을 말한다.

 

내부 반복자를 사용해서 얻는 이점은 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가에 대해서는 개발자가 신경 쓰지 않고, 각 요소를 어떻게 처리할 것인가에 대해서만 집중할 수 있다는 것이다.

더불어 내부 반복자는 요소들의 반복 순서를 변경하거나 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게끔 구현되어 있기 때문에 하나씩 처리하는 순차적 외부 반복자보다는 훨씬 효율적으로 프로그래밍할 수 있다.

이 글에서는 자세하게 다루지 않겠지만, 병렬 처리 스트림에 대해 더 궁금한 분들은 parallelStreamForkJoinPool에 대해 더 찾아보길 바란다.

 

이처럼 스트림의 종류는 Stream만 있는 것이 아니다.

이제부턴 자바 8에서 새롭게 추가된 스트림의 다양한 종류와 스트림 객체를 어떻게 생성할 수 있는지 알아보겠다.

 

스트림(Stream) 종류와 객체 생성

종류

참고로 스트림 API는 java.util.stream 패키지에 정의되어 있다.

패키지 내용을 보면, BaseStream 인터페이스를 부모로 하여 그 자식으로 각각 Stream, IntStream, LongStream, DoubleStream이 있다. 이름만 봐도 알겠지만 Stream은 제네릭으로 구현되어 있고 IntStream, LongStream, DoubleStream은 각각의 이름에 맞는 primitive type인 int, long, double 요소를 처리하는 스트림으로 이해하면 되겠다.

 

객체 얻기

Stream 인터페이스의 구현(Concrete) 객체는 다양한 소스로부터 얻을 수 있다.

주된 방법으로는 앞선 예제와 같이 Collection의 stream() 메소드 혹은 Arrays.stream(T[]) 메소드로 얻는 것이지만, 이외에도 Stream.of(T[]), Files.lines(Path, Charset), BufferedReader.lines() 등의 방법이 있다.

 


이번 포스팅에서는 스트림의 간단한 소개와 특징들을 살펴보았다.

다음 포스팅에서는 스트림이 제공하는 각각의 메소드들에 대해 조금 더 자세히 살펴보겠다.

반응형
반응형