alt
Home 자바의정석 - 14장 스트림
Post
Cancel

자바의정석 - 14장 스트림

: 데이터 소스를 추상화하고 자주 사용되는 메서드들을 정의해 놓은 것


특징

  • Side effect: 기존 데이터 소스를 변경하지 않는다. 따라서 stream을 사용하면 여러 스레드가 동시접근해도 임계영역 문제를 줄일 수 있다.

  • 일회용: 한번 사용하면 스트림이 닫혀서 재사용 불가능하다. 필요하면 스트림을 다시 생성해야한다.

  • 데이터 소스 추상화 : 데이터 소스가 무엇이던 같은 방식으로 다룰 수 있다. 원래같으면 List 정렬에는 Collections.sort(), Array 정렬에는 Arrays.sort() 사용해야한다.

  • 지연 연산(lazy evaluation): 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다. 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다.

    1
    2
    3
    4
    5
    6
    
    List<String> names = Arrays.asList("a", "b", "c");
      
    lists.stream().map(name -> {
      System.out.println(name); // *
      return name.toUpperCase();
    });
    
    • 위 코드에서 map()이라는 중간 연산만 존재하고 최종 연산이 없기 때문에 * 출력문은 수행되지 않는다.
  • 오토박싱 & 언박싱의 데이터 소스의 요소를 기본형으로 다루는 스트림 (IntStream, LongStream, DoubleStream)이 제공된다.
    • 오토박싱과 언박싱: int -> Integer, Integer -> int
  • 병렬 스트림: 스레드 병렬 처리를 지원한다. parralel() 로 병렬 처리, sequential()로 병렬 처리하지 않도록.


스트림 생성

  • Collection.stream() , Stream.of(T... values) 과 같이 생성

  • 기본형 스트림(박싱되어있는 스트림)이 일반 스트림보다 보편적으로 좋다. e.g) IntStreamStream<Int>보다 좋다. 해당 타입의 값으로 작업하는데 유용한 메서드들이 포함되어있다.

    • 중간 연산인 mapTo() 를 통해 기본형 스트림으로 변경해준다.
  • iterate(), generate(): 람다식을 매개변수로 받아, 이 람다식에 의해 계산되는 값들을 요소로하는 무한 스트림을 생성

    1
    2
    3
    4
    5
    6
    
    Stream<Double> randomStream = Stream.generate(Math::random);
    Stream<Double> oneStream = Stream.generate(() -> 1));
      
    IntStream evenStream = Stream.iterate(0, n -> n + 2); // A. 에러
    IntStream evenStream = Stream.iterate(0, n -> n + 2).mapToInt(Integer::valueOf);
    Stream<Integer> stream = evenStream.boxed(); // IntStream -> Stream<Integer>
    
    • A : iterate(), generate() 에 의해 생성된 스트림을 기본형 스트림 타입의 참조변수로 다룰 수 없다.


Optional

  • 객체 생성: Optional.of() or Optional.ofNullable()
  • 객체 값 가져오기: optionalObj.get(). Null이면 NPE 발생하기 때문에 orElse() 로 값 대체 or orThrow()
  • isPresent() 를 통해 조건식 가능. ifPresent() 를 사용하면 더 간결하다. ifPresent()findAny(), findFirst() 같은 최종 연산과 잘 어울린다.


중간 연산

  • 연산 결과가 스트림이다.
  • 스트림 자르기 (앞) skip(), (뒤) limit(), 요소 걸러내기 filter(), distinct(), 조회 peek(): 스트림 중간에 로그 찍기 용도 등.
  • flatMap(): Stream<T[]> -> Stream<T>


최종 연산

  • 연산 결과가 스트림이 아니다.
  • loop forEach(), 조건 검사 ~~~Match(), find~~~(), 통계나 연산count(), sum(), average(), max(), min()
  • 리듀싱: reduce(). 스트림의 요소를 줄여나가면서 연산을 수행하고 최종 결과 반환한다.
    • 매개변수 타입이 BinaryOperator<T>이다. 처음 두 요소를 가져와 연산을 수행하고 그 결과를 가지고 다음 요소와 연산한다.
    • count(), sum(), max(), min()은 내부적으로 리듀싱을 사용한다.


collect

: 스트림의 요소를 수집하는 최종 연산. reducing과 유사하다.


Collectors 메서드

  • 스트림을 컬렉션, 배열로 변환: toList(), toSet()

  • 통계: counting(), summingInt(), averaginInt(), maxBy(), minBy()

  • 리듀싱: reducing()

  • 문자열 결합: joining() 구분자, 접두사, 접미사 지정도 가능하다.

  • 그룹화와 분할: groupingBy(), partitioningBy(). 이 두 가지가 collect() 가 필요한 가장 큰 이유이다. 둘은 분류를 Function으로 하느냐, Predicate로 하느냐의 차이만 있을 뿐 동일하다.

    • groupingBy(): 특정 요소를 기준으로 그룹화.
    • partitioningBy(): 지정된 조건에 일치하는 그룹과 아닌 그룹 두가지로 분할. Predicate를 매개변수로 받는다. 두 개의 그룹으로 나눠야 한다면 당연히 PartitioningBy()를 사용하는 것이 빠르다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    stream.collect(summingInt(Foo::getNumber));
      
    // -- 아래 두 방식은 동일한 결과
      
    stream.map(Foo::getNumber).reduce(0, Integer::sum);
    stream.collect(reducing(0, Foo::getNumber, Integer::sum));
      
    // --- 그룹화와 분할 예시
      
    Map<Boolean, Long> stuNumBySex = stuStream
      					.collect(partitioningBy(Student::isMale));
    Map<Integer, List<Student>> stuByClass = stuStream
      					.collect(groupingBy(Student::getClass));
    Map<Integer, Map<Integer, List<Student>>> stuByGradeAndClass = stuStream
      					.collect(groupingBy(Student::getGrade,
                                   groupingBy(Student:getClass)
                                   ));
    


Collector 구현

: “컬렉터를 작성한다” == “Collector 인터페이스를 구현한다.”

  • supplier(), accumulator(), combiner(), finisher(), characteristics() 중 앞 네 개는 함수형 인터페이스이다. 따라서 해당 부분에 람다식을 작성하면 된다.
  • 앞의 세 개 인터페이스는 reduce 인터페이스에서도 등장하는 개념이다. 따라서, 내부적으로 처리하는 과정이 리듀싱과 같다. 그룹화와 분할, 집계 등에 collector가 유용하게 쓰이고, 병렬화에 있어서도 collect()가 유리하다.



Reference)

자바의 정석 3판

This post is licensed under CC BY 4.0 by the author.

자바의정석 - 14장 람다식

환경별로 Spring 설정 구성하기 (on-profile)