[모던 액션 인 자바] 04. 스트림 소개

seony·2023년 3월 4일
0

모던 자바 인 액션

목록 보기
4/6
post-thumbnail
  • SQL 질의 언어에서는 우리가 기대하는 것이 무엇인지 직접 표현할 수 있다.
    • 즉, SQL에서는 질의를 어떻게 구현할지 명시할 필요가 없으며 구현은 자동으로 제공한다.
  • 성능을 높이려면 멀티코어 아키텍처를 활용해서 병렬로 컬렉션의 요소를 처리해야 한다.

스트림의 등장

4.1 스트림이란 무엇인가?

  • 스트림은 자바 8 API에 새로 추가된 기능이다.
  • 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다.
    • 즉, 질의로 표현할 수 있다.
  • 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.

스트림 API 요약

  • 선언형
    • 더 간결하고 가독성이 좋아짐
  • 조립할 수 있음
    • 유연성이 좋아짐
    • filter, sorted, map, collect 등 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프 라인을 만들 수 있다.
  • 병렬화
    • 성능이 좋아짐

4.2 스트림 시작하기

스트림 정의

  • 스트림이란
    • 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
  1. 연속된 요소
    • 컬렉션: 시간과 공간의 복잡성과 관련된 요소 저장접근 연산이 중점
    • 스트림: filter, sorted, map처럼 표현 계산식이 중점
  2. 소스
    • 리스트로 스트림을 만들면 스트림의 요소는 리스트의 요소와 같은 순서를 유지
  3. 데이터 처리 연산
    • 함수형 프로그래밍 언어, 데이터베이스에서 지원하는 연산을 지원
      • filter, map, reduce, find, match, sort
    • 스트림 연산은 순차적으로 or 병렬로 실행 가능

스트림 2가지 주요 특징

  1. 파이프라이닝
    • 대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프 라인을 구성할 수 있도록 스트림 자신을 반환한다.
      게으름(layziness), 쇼트서킷(short-circuiting) 같은 최적화 얻을 수 있음
  2. 내부 반복
    • 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.

<지금까지 설명한 내용 예시>

import static java.util.stream.Collectors.toList;
List<String> threeHighCaloricDishNames =
    menu.stream() // 메뉴(요리 리스트)에서 스트림 얻기
        .filter(dish -> dish.getCalories() > 300) // 파이프라인 연산 만들기
        .map(Dish::getName) // 요리명 추출
        .limit(3) // 선착순 3개
        .collect(toList()); // 결과를 다른 리스트로 저장
System.out.println(threeHighColoricDishNames); // [pork, beef, chicken]
  • 데이터 소스 ➜ 요리 리스트(메뉴)
    • 연속된 요소를 스트림에 제공
  • filter, map, limit, collect로 이어지는 일련의 데이터 처리 연산 제공
    • collect를 제외한 모든 연산은 서로 파이프라인을 형성할 수 있도록 스트림을 반환
제목내용
filter람다를 인수로 받아 스트림에서 특정 요소를 제외시킨다.
map람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출한다.
limit스트림 크기를 축소(truncate)한다.
collect스트림을 다른 형식으로 변환한다.

4.3 스트림과 컬렉션

  • 자바의 기존 컬렉션과 새로운 스트림 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스 제공
    • 연속된 : 순차적으로 값에 접근한다는 것

데이터 처리

  • 스트림
    • 요청할 때만 요소를 계산하는 고정된 자료구조
    • 사용자가 요청한 값만 스트림에서 추출함
    • 스트림은 게으르게 만들어지는 컬렉션과 같다. (요청 중심 제조)
      • 즉, 사용자가 데이터를 요청할 때만 값을 계산
    • 한 번만 탐색할 수 있음
      • 다시 탐색하려면 새로 스트림 만들어야 한다.
  • 컬렉션
    • 현재 자료 구조가 포함하는 모든 값을 메모리에 저장하는 자료구조. 즉, 컬렉션에 추가하기 전에 계산되어야 함
    • 적극적으로 생성된다. (생산자 중심 제조)

내부 반복, 외부 반복

  • 스트림
    • 내부 반복: 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장
  • 컬렉션
    • 외부 반복: 사용자가 직접 요소를 반복해야 한다. (예를 들면 for-each)

내부 반복 장점

  • 작업을 투명하게 병렬로 처리
  • 최적화된 다양한 순서로 처리
  • 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택
    • 외부 반복에서는 병렬성을 스스로 관리해야 한다.

4.4 스트림 연산

  • 연결할 수 있는 스트림 연산을 중간 연산이라 하며, 스트림을 닫는 연산을 최종 연산이라고 한다.
List<String> names = menu.stream()
						 .filter(dish -> dish.getCalories() > 300) // 중간 연산
                         .map(Dish::getName) // 중간 연산
                         .limit(3) // 중간 연산
                         .collect(toList()); // 스트림을 리스트로 변환

중간 연산

  • filtersorted 같은 중간 연산은 다른 스트림을 반환
  • 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것
    • 즉, 게으르다
    • 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한 번에 처리하기 때문

게으른 특성 덕분에 얻을 수 있는 최적화 효과

  • 쇼트 서킷

    • 모든 연산을 다 해보기 전에 조건을 만족하면 추가적인 불필요한 연산은 하지 않는다
    • 위의 예시에서는 limit 연산이 쇼트 서킷 연산에 해당된다.
    • 3개의 결과를 얻은 후 앞선 filter와 map연산은 더 이상 수행할 필요가 없어 빠르게 최종 연산을 수행한다.
  • 루프 퓨전

    • 위의 예시 코드에서 filter와 map 연산에 값을 print 하는 과정을 추가한다면 filter와 map이 다른 연산이지만 한 과정으로 병합되어 처리됨을 확인할 수 있다.
    • 루프 퓨전은 이렇게 둘 이상의 연산이 합쳐 하나의 연산으로 처리됨을 말한다.

최종 연산

  • 최종 연산은 파이프라인에서 결과를 도출한다.
  • 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다.

마치며

  • 스트림은 소스에서 추출된 연속 요소로, 데이터 처리 연산을 지원한다.
  • 스트림은 내부 반복을 지원한다.
  • 스트림은 중간 연산최종 연산이 있다.
  • 중간 연산으로는 어떤 결과도 생성할 수 없다.
  • 스트림의 요소는 요청할 때 게으르게 계산된다.

0개의 댓글