본문 바로가기

Programming/Java

[Java] 스트림(Stream)

스트림

스트림(Stream)은 Java8부터 추가된 컬렉션(배열 포함)의 요소를 하나씩 참조해 람다식으로 처리할 수 있도록 하는 반복자이다.

데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소

 

List<String> list = Arrays.asList("A", "B", "C", "D");

// 스트림 사용❌
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
    String name = iter.next();
    System.out.println(name);
}

// 스트림 사용⭕
Stream<String> stream = list.stream();
list.stream().forEach(name -> System.out.println(name));

 

특징

선언형

  • 선언형으로 코드를 구현할 수 있어 변하는 요구사항에 쉽게 대응이 가능
  • 대부분의 요소 처리 메소드는 함수적 인터페이스 매개 타입이다
  • 간결하고 가독성이 향상

 

조립 가능

  • 빌딩 블록 연산을 연결해 데이터 처리 파이프라인을 만들 수 있어 가독성과 명확성이 유지
  • 중간 처리(map, fliter, sort)와 최종 처리(forEach, count, sum) 등의 처리가 가능

 

병렬화

  • 내부 반복자를 사용해 병렬처리가 쉬워진다
  • 개발자는 요소당 처리해야할 코드만 작성하고 요소의 반복은 컬렉션에게 위임한다
  • 병렬로 실행이 가능해 성능이 향상
  • 요소들의 반복 순서를 변경하거나 CPU를 최대한 활용하기 위해 병렬 작업을 하도록 돕기 때문에 한 개씩 처리하는 외부 반복자보다 효율적이다
  • 스레드와 락을 걱정하지 않아도 된다

 

💡 병렬(parallel) 처리

한가지의 작업을 서브 작업으로 나누고 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것
런타임시 하나의 작업을 서브 작업으로 자동으로 나누고, 서브 작업의 결과를 결합해 최종 결과물을 생성

- 순차 처리 스트림 -> 하나의 스레드가 요소들을 순차적으로 읽고 합을 구함
- 병렬 처리 스트림 -> 여러 개의 스레드가 요소들을 부분적으로 합하고 최종 결합해 전체의 합을 생성

 

일회성

  • 스트림은 한 번만 탐색이 가능하고 탐색된 스트림은 소비
  • 다시 탐색하려면 새로운 스트림을 생성해야 함
List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // java.lang.IllegalStateException 발생

 

외부 반복 & 내부 반복

사용자가 직접 요소를 반복하는 것을 외부 반복(external iteration), 스트림은 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장해주는 내부 반복(internal iteration)을 사용한다.

 

// 외부 반복
List<String> list = new ArrayList<>();
Iterator<String> iter = member.iterator();

while(iter.hasNext()){
    Member member = iter.next();
    if(member.getAge() >= 20){
        list.add(member.getName());
    }
}

// 내부 반복
List<String> list = member.stream()
    				.filter(m -> m.getAge() >= 20)
    				.collect(toList());

 

내부 반복을 사용하면 작업을 투명하게 병렬적으로 처리하거나 최적화된 다양한 순서로 처리가 가능하다. 외부 반복에서는 병렬성을 스스로 관리(synchronized 사용)해야 한다.

 

스트림 연산

중간 연산

중간 처리 메소드는 중간 처리된 스트림을 리턴하고, 이 스트림에서 다시 중간 처리된 메소드를 호출할 수 있어 파이프라인을 형성한다.

특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다 => 게으르다(lazy)

중간 연산을 합친 다음 합쳐진 중간 연산(loop fusion)을 최종 연산으로 한번에 처리한다.

 

ex) filter, map, limit, sorted, distinct, peek ...

 

최종 연산

스트림 파이프라인에서 기본형이나 Optional 등 스트림 이외의 결과를 도출한다.

 

ex) forEach, count, collect, match ...

 

 

 


📚 Reference

신용권, 『이것이 자바다』, 한빛미디어(2015)
라울-게이브리얼 우르마, 『모던 자바 인 액션』, 우정은, 한빛미디어(2018)

 

'Programming > Java' 카테고리의 다른 글

[Java] String.replaceAll() 활용하기  (0) 2021.05.19
[Java] 스트림(Stream) 활용  (0) 2021.05.13
[Java] 람다(lambda)  (0) 2021.05.06
[Java] Boxing, Unboxing  (0) 2021.05.05
[Java] Arrays.sort(), Collections.Sort() 속도 비교  (0) 2021.03.04