카테고리 없음

스트림(Stream)의 특징

sanook 2021. 8. 15. 23:55

스트림이란 ?

  • 순차 및 병렬 애그리게이트 연산을 지원하는 데이터 소스에서 추출된 일련의 요소
  • 어떻게 동작을 구현할 것인지 지정하지 않고 선언적으로 컬렉션 데이터를 처리 가능한 기능
    • 만약 카페에서 카페인이 없는 메뉴의 이름들을 알고 싶다면 어떻게 해야 할까?
      • List<String> caffeineFreeDrinkNameList = new ArrayList<>();
        Iterator<Drink> iterator = Drink.menu.iterator();
        while(iterator.hasNext()) {
        	Drink drink = iterator.next();
        	if(drink.hasCaffeine() != true) caffeineFreeDrinkNameList.add(drink.getName());
        }
    • 스트림으로 구현한다면 이렇게 바꿀 수 있다
      • List<String> caffeineFreeDrinkNameList = Drink.menu.stream()
        		                                                .filter(d -> d.hasCaffeine() != true)
        		                                                .map(Drink::getName)
        		                                                .collect(toList());
  • 생산자(Producer)와 소비자(Consumer)의 관계를 형성하여 사용자가 요청하는 값만 스트림에서 추출

특징

  • 파이프라이닝
    • 터미널 작업이 아닌 중간 작업은 스트림을 반환하여 스트림 연산 끼리 연결하여 데이터 처리 파이프라인을 구성할 수 있다
    • 루프 퓨전 : 서로 다른 스트림 연산이 한 과정으로 병합됨
    • 중간 작업
      • 초기 스트림 요소를 포함하는 다른 스트림으로 반환한다
      • 게으름(laziness) : 터미널 작업이 시작될 때 까지 실제로 동작하지 않는다
      • 쇼트서킷(short-circuiting) : 전체 스트림을 처리 하지 않아도 원하는 요소를 찾았으면 즉시 결과를 반환할 수 있는 기법
      • 예시 : filter()
    • 터미널 작업
      • 스트림 파이프라인에서 결과를 도출하는 연산
      • 터미널 작업을 수행한 후에는 스트림 파이프라인이 사용된 것으로 간주되어 더 이상 사용할 수 없다
      • 스트림은 한 번만 동작해야 함으로 동일한 데이터 소스를 다시 사용해야 하는 경우 데이터 소스로 돌아가 새 스트림을 가져와야 한다
      • 예시 : collect()
  • 내부 반복
    • 반복을 알아서 처리하고 결과 스트림 값을 어딘가에 저장해주는 것
    • 외부 반복의 경우 병렬성을 스스로 관리 해야 하지만 병렬성 구현을 자동으로 선택 및 최적화된 다양한 순서 처리가 가능하며 스레드와 락을 걱정할 필요가 덜어진다
  • 비간섭
    • 스트림을 사용하면 ArrayList와 같이 스레드에 안전하지 않은 컬렉션 비롯한 다양한 데이터 소스에서 병렬로 어그리게이트 작업을 실행할 수 있다
    • iterator()과 spliterator()를 제외하고 스트림의 실행은 터미널 작업과 생애주기가 같음으로 터미널 작업이 시작되기 전에 소스를 수정할 수 있으며 이러한 수정 사항이 적용된 요소에 반영되고 스트림 파이프라인을 실행하는 동안 데이터 소스가 전혀 수정되지 않도록 한다
  • 상태없음
    • 내부 상태를 갖는 연산
      • 스트림의 내부 상태의 크기는 한정 되어 있는데 최대값을 구하거나(max()), 중복을 제거하려면(distinct()) 과거 이력을 알고 있어야 함으로 이전 요소가 버퍼에 추가 되어 있어야 한다
    • 상태가 없는 연산
      • 연산의 파라미터인 람다나 메소드 참조가 내부적인 가변 상태를 가지지 않는다면 map(), filter()와 같이 입력 스트림에서 각 요소를 받아 내부적 상태 고려 없이 0 또는 출력 스트림을 반환하는 연산을 의미함
      • 내부적인 가변 상태를 람다나 메소드 참조가 가지고 있다면 병렬로 수행할 경우 스레드 스케줄링 차이로 인해 동일한 입력에 대한 결과가 실행마다 다를 수 있지만 상태를 비 저장한다면 결과가 항상 동일하다

 

참고 자료 : 

라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트 저/우정은 역, 모던 자바 인 액션 (한빛미디어) 4장, 5장

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html