[자바 인 액션] Ch 6

Ericamoyed·2021년 7월 19일
0

자바인액션

목록 보기
6/6

스트림으로 데이터 수집

요약

  • Collect(Collectors.toList())
  • Collect(Collectors.counting())
  • Collect(Collectors.summarizingInt())
  • Collect(Collectors.joining(","))
  • Collect(Collectors.reducing(0, ~::getValue, Integer::Sum))
  • Collect(Collectors.groupingBy(Transaction::getCurrency))
  • 스트림이 비었을 때를 대비해 Optional이 반환되곤 하는데, .orElse()등을 사용하여, 간접적으로 초깃값을 제공하는 것이 바람직하다.

연산 종류

중간 연산

  • 한 스트림을 다른 스트림으로 변환하는 연산
  • 여러 연산 연결 가능
  • 스트림 파이프라인 구성

최종 연산

  • 스트림의 요소를 소비해서 최종 결과 도출
  • 재사용 불가

groupingBy

List<Transaction> transactions;
transactions.stream().collect(groupingBy(Transaction::getCurrency));
  • 각 트랜잭션의 Currency 별로 그룹핑된 map이 반환됨
  • 각 키, 그리고 각 키 벜닛에 대응하는 요소 리스트를 값으로 포함하는 Map을 만들라 !

기타

  • Collect 메서드로 Collector 인터페이스 구현을 전달
  • groupingBy도 Collector 인터페이스 구현 중 하나. Collectors.toList() 얘네들도 다 Collector 인터페이스 구현
  • Collectors 유틸리티 클래스는 자주 사용하는 컬렉터 인스턴스를 손쉽게 생성할 수 있는 정적팩토리메서드를 제공 as (Collectors.toList())

Collector 인터페이스 구현체

summarization 연산

숫자 필드의 합계/평균 등 반환하는 연산

  • counting()
    • stream().collect(Collectors.counting())
    • stream().count()
    • 상기 두개는 동일한 결과를 도출
  • maxBy()
    • comparator 구현체를 maxBy의 parameter로 넘겨서 최대값 반환
  • summingInt()
    • stream().collect(summingInt(Dish::getCalories));
  • summarizingInt()
    • stream().collect(summarizingInt(Dish::getCalories));
    • count, sum, min, average, max -> 모든 summarization 연산의 결과가 IntSummaryStatistics 클래스로 수집된다

문자열 연결

  • joining()
    • 각 객체에 toString() 메서드를 호출해서, 호출한 모든 문자열을 하나의 문자열로 연결하여 반환
    • 내부적으로 StringBuilder를 이용하여 문자열을 하나도 만듦
    • cf) StringBuilder/String/StringBuffer의 차이 -> 부록에서 확인 가능
    • .collect(joining(", "))
      • pork, beef, chicken, ...

범용 리듀싱

  • Collectors.reducing
  • 사실 위에서 언급한 연산들 모두 reducing을 사용해서 정의가 가능하다.
  • summarization 연산에서 사용하던 sum은 reducing을 이용하면 이런식으로 구현 가능
  • reducing(0, Dish::getCalories, (i,j) -> i+j))
  • .collect(Collectors.reducing())은 사실 .reduce()와 닮아있다. 생각해보니 그러면 reduce로도 모든 Collector 구현체를 구현할 수 있는것 아닌가 ?
    • 그러면 이제, reducing으로 하는게 나은지, 상기 메소드들을 사용하는게 좋은지 의문이 들겠지? 두개는 사실 설계 목적부터 달랐다고 함
    • collect 메서드는 도출하려는 결과를 누적하는 컨테이너를 바꾸도록 설계된 메서드
    • reduce는 두 값을 하나로 도출하는 불변형 연산
    • reduce 메서드는 thread-unsafe로, 여러 스레드가 동시에 같은 데이터 구초레를 고치면 리듀싱 연산 병렬 수행이 불가능
    • 따라서 병렬성 확보를 위해서는 collect 메서드로 리듀싱 연산을 구현하는 것이 바람직

그룹화

  • groupingBy
    • 보통 메서드 참조를 분류함수로 사용하나, 복잡해서 어려울 경우에는 람다 표현식으로 사용하면 된다.
    • 지금까지 개발하면서는, 얘 파라미터가 하나인줄만 알았겠지만 사실 여러 파라미터를 받을 수 있다.
    • menu.stream()
      .filter(dish -> dish.getCalories()>500)
      .collect(groupingBy(Dish::getType))
    • 얘가 우리가 일반적으로 아는 파라미터하나인 groupingBy
    • 한계가 있다면, 필터 프리디케이트를 만족하지 못하는 요소는 아예 결과 맵에서 키 자체가 없어진다는 점
    • 하지만 이렇게 변경하면, 키는 유지하고 내부 value만 없애는 식으로 변경할 수 있다.
    • menu.stream()
      .collect(
        groupingBy(Dish::getType
          ,filtering(dish -> dish.getCalories() > 500
            , toList())
        )
      )
    • groupingBy 내에서 두번째 파라미터로 mapping()을 넘길 수도 있고, 유사하게 flatMapping()도 사용할 수 있다. (coungting()도 사용 가능)
      • flatMappiung을 사용할 경우, 연산 결과를 수집해서 리스트가 아니라 집합으로 그룹화해 중복 태그를 제거한다.
      • menu.stream().collect(groupingBy(Dish::getType
        , flatMapping(
           dish -> dishTags.get(dish.getName()).stream()
          , toSet())
        ));
    • groupingBy를 다중으로 사용하여 n-수준 groupingBy -> 결과는 n-수준 Map으로 배핑 가능하다.
      • groupingBy의 두번째 파라미터에 또 groupingBy를 넘기는 방식 !

분할

  • 프리디케이트를 분류 함수로 사용하여, 결과가 참인것과 거짓인 것으로 Map을 반환한다.
  • 장점은, 참/거짓 두가지 요소의 스트림 리스트를 모두 유지하며 반환해준다는 것 !
  • partitioningBy(Dish::isVegetarian
    , groupingBy(Dish::getType)
    -> 2수준 Map을 반환 (초기 key 값은 true/false로 분류, 그 내부에서 type으로 분류된 Map)

Collector 인터페이스 직접 구현하기

  • 상기 커스텀하게 제공되는 걸로 충분하지 않으면 직접 구현해서 사용하면 되는데
  • 이럴 상황이 있을지는 잘 모르겠어서 그냥 대충 봤음.. 왠만하면 스트림 기본 제공 기본 구현체로 다 되는데..
  • 구성은 supplier(), accumulator(), finisher(), combiner(), characteristics()
  • supplier -> 수집 과정에서 빈 누적자 인스턴스를 만드는 파라미터가 없는 함수
  • accumulator -> 누적자와 n번째 요소를 함수에 적용하는 BiConsumer로 구성
  • finisher -> 누적자 객체를 최종 결과로 변환하면서 누적 과정을 끝낼 때 호출하는 함수. 딱히 별거 없으면 Function.identity() 하면 될듯
  • combiner -> 두 결과 컨테이너 병합 / 얘는 스트림의 서로 다른 서브파트를 병렬로 처리할 때만 명시해주면 됨
  • Characteristics -> 스트림의 리듀스 방식 (병렬 여부) 또는, 병렬이라면 어떤 최적화로 리듀스 할지 힌트 제공

부록

StringBuilder/String/StringBuffer의 차이

  • String: immutable 객체 / thread-safe
    • 메모리에 생성 후 값 변경 불가능
    • heap의 StringPool에 저장되고, 계속 남아있다가 GC로 사라짐
    • immutable의 특성으로, 수정이 용이하지 않고 메모리의 낭비 초래
    • thread-safe이므로 병렬 및 동기화에 케어 안해도됨
  • StringBuffer: mutable 객체 / thread-safe
    • 메모리에 생성 후 값 변경 가능
    • 마찬가지로 heap영역에 저장되나, StringPool로는 사용 X
    • thread-safe이므로 병렬 및 동기화에 케어 안해도됨
  • StringBuilder: mutable 객체 / thread-unsafe
    • 메모리에 생성 후 값 변경 가능
    • 마찬가지로 heap영역에 저장되나, StringPool로는 사용 X
    • thread-unsafe이므로 병렬처리 시 동기화 코드 구현 필요
    • 하지만 단일스레드 환경에서는 StringBuffer보다 성능 good (thread 신경쓰지 않도록 코드가 구성돼있기 때문)
profile
꿈많은 개발자, 일상 기록을 곁들인

1개의 댓글

comment-user-thumbnail
2024년 7월 1일

귀하의 게시물은 정말 귀중한 게시물입니다. 공유해 주셔서 감사합니다. 저는 게임 매니아입니다. 게임을 좋아한다면 이 웹사이트가 도움이 될 것입니다 ( https://www.ssegold.com/ ). 나는 당신에게 행복한 삶을 기원합니다.

답글 달기

관련 채용 정보