본문 바로가기

프로그래밍/Book

[모던 자바 인 액션] 5장. 스트림 활용

필터링

스트림의 요소를 선택하는 방법

  • filter: Predicate를 인수로 받아서 일치하는 모든 요소를 포함하는 스트림 반환
  • distinct: 고유 요소로 이루어진 스트림 반환(중복복을 제거하며 객체의 hashcode, equals로 결정)

스트림 슬라이싱

스트림의 요소를 선택하거나 스킵하는 방법

Predicate를 이용한 슬라이싱

자바 9 에서는 takeWhile, dropWhile 두가지의 새로운 메서드를 제공한다

TAKEWHILE

책 리스트가 최신순으로 정렬이 되어있다.

List<Book> books = asList(new Book("2022신상책", "양승인", 2022, "IT"),
        new Book("이순신의 바다", "황현필", 2021, "History"),
        new Book("갈매기의 꿈", "리처드 바크", 2020, "Fiction"),
        new Book("모던 자바 인 액션", "라울-게이브리얼 우르마", 2020, "IT"),
        new Book("2019년 책", "양승인", 2019, "IT"),
        new Book("이펙티브 자바", "조슈아 블로크", 2018, "IT"));

2020년도 부터 발행된 책을 선택하려면 filter를 사용할 수 있었다.

List<Book> collect = books.stream()
                .filter(book -> book.getYear() >= 2020)
                .collect(Collectors.toList());

하지만 주어진 books는 이미 정렬이 되어있고, 순회하면서 2019년을 만난 이후 시점의 책을 반복하는 것은 낭비이다.
이때 takeWhile을 이용하면 predicate를 만족하지 못하는 요소를 만나면 중한 후 스트림을 반환한다.

List<Book> collectWithTakeWhile = books.stream()
        .takeWhile(book -> book.getYear() >= 2020)
        .collect(Collectors.toList());

DROPWHILE

takeWhile과 정반대의 작업을 수행한다.
predicate가 처음으로 거짓이 되면 그 지점에서 작업을 중단하고 남은 요소를 반환한다.

List<Book> collectWithDropWhile = books.stream()
        .dropWhile(book -> book.getYear() >= 2020)
        .collect(Collectors.toList());

스트림 축소

limit

  • 함수에 파라미터로 넣은 건수만큼의 요소를 반환한다.

요소 건너뛰기

skip

  • 처음 n개의 요소를 제외한 스트림을 반환한다.

매핑

특정 객체에서 특정 데이터를 선택하는 기능

스트림의 각 요소에 함수 적용하기

map

  • Function을 인수로 하며 받은 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑
List<String> bookNames = books.stream().
                map(Book::getName)
                .collect(Collectors.toList());

스트림 평면화

flatMap

  • 스트림의 요소를 풀어서 하나의 평면화된 스트림으로 반환.
List<String> collect = helloWorld.stream()
                .map(s -> s.split(""))
                .flatMap(Arrays::stream)
                .collect(Collectors.toList());

검색과 매칭

특정 속성이 데이터 집합에 있는지를 검색하는 데이터 처리

Predicate가 적어도 한요소와 일치하는지 확인

anyMatch

  • Predicate가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인.
  • boolean을 반환하는 최종 연산

Predicate가 모든 요소와 일치하는지 검사

allMatch

  • 스트림의 모든 요소가 주어진 Predicate와 일치하는지 확인
  • boolean을 반환하는 최종 연산

noneMatch

  • allMatch와 반대 연산
  • 주어진 Predicate와 일치하는 요소가 없는지 확인
  • boolean을 반환하는 최종 연산

요소 검색

findAny

  • 현재 스트림에서 임의의 요소 반환
  • Optional 객체를 리턴하며 요소가 없을 시 Optional.empty() 반환

첫 번째 요소 찾기

findFirst

  • 스트림의 첫 번째 요소 반환

findFirst와 findAny는 언제 사용할까?

병렬실행에서는 첫 번째 요소를 찾기 어려워서 반환 순서가 상관없으면 병렬 스트림에서는 제약이 적은 findAny를 사용

리듀싱

모든 스트림 요소를 처리해서 값으로 도출

reduce

  • 두 개의 인수를 가짐
  • 초깃값에서 시작하여 새로운 값을 만드는 BinaryOperator를 통해 결과가 하나의 값으로 줄어들 때 까지 반복하여 조합
  • 초깃값이 없도록 오버로드된 reduce도 존재 (Optional 반환)

상태 없는 연산과 상태 있는 연산

상태 없는 연산

  • 입력 스트림에서 요소를 받아 결과를 출력스트림으로 보냄
  • 상태를 저장하지 않음
  • filter, map 등

상태 있는 연산

  • 연산 결과를 누적할 상태가 필요하여 상태를 저장하고 있음
  • reduce, sorted, distinct 등

숫자형 스트림

숫자 연산을 할 때 Integer 타입은 기본형으로 언박싱 해야되서 박싱 비용이 발생한다. 이런 비용을 줄이기 위해 스트림API는 숫자 스트림을 효율적으로 처리하기 위해 기본형 특화 스트림을 제공한다.(IntStream, DoubleStream, LongStream)

숫자 스트림으로 매핑

스트림을 특화 스트림으로 변환할 때 mapToInt, mapToDouble, mapToLong 메서드를 사용하며 변환한 특화 스트림은 sum, max, min, average등의 메서드를 제공한다.

//책들의 평균 출판연도
OptionalDouble average = books.stream()
                .mapToInt(Book::getYear)
                .average();

객체 스트림 복원

숫자 스트림으로 만든 후 boxed 메서드를 사용하여 숫자 스트림을 원래의 스트림으로 복원할 수 있다.

IntStream intStream = books.stream().mapToInt(Book::getYear);
Stream<Integer> boxed = intStream.boxed();

숫자 범위

특정 범위의 숫자를 이용해야 할 때 IntStream과 LongStream에서는 range, rangeClosed라는 정적 메서드를 제공한다.

IntStream evenNumbers = IntStream.rangeClosed(1, 100)
        .filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());

스트림 만들기

값으로 스트림 만들기

Stream.of()

  • 메소드에 전달한 값으로 만든 스트림 반환

Stream.empty()

  • 빈 스트림 반환
Stream<String> alphabet = Stream.of("A", "B", "C", "D", "E");
Stream<Integer> evenNumber = Stream.of(2, 4, 6, 8, 10);
Stream<Object> empty = Stream.empty();

null이 될 수 있는 객체로 스트림 만들기

Stream.ofNullable()

  • null이 될 수 있는 객체를 스트림 반환

배열로 스트림 만들기

Arrays.Stream()

  • 배열을 인수로 받아 스트림 생성
    int[] num = {1, 2, 3, 4, 5, 6};
    int sum = Arrays.stream(num).sum();

파일로 스트림 만들기

Files.lines()

  • 파일의 각행 요소를 반환하는 스트림 반환
    long uniqueWords = 0;
    try (Stream<String> lines =
               Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
      uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
              .distinct()
              .count();
    } catch (IOException e) {
    }

함수로 무한 스트림 만들기

스트림 API는 함수에서 스트림을 만들 수 있는 두 정적 메소드 Stream.iterate, Stream.generate를 제공

Stream.iterate()

  • 초깃값과 람다를 인수로 하여 요청할 때마다 주어진 함수를 통해 값을 만든다.
  • 기존 결과에 의존하여 순차적으로 연산을 수행
Stream.iterate(0, n -> n + 2)
    .limit(10)
    .forEach(System.out::println);

Stream.generate()

  • 무한 스트림을 만들 수 있지만 iterate와 다른점은 생산된 값을 연속적으로 계산하지 않는다.
Stream.generate(Math::random)
        .limit(10)
        .forEach(System.out::println);

참고문서

모던 자바 인 액션 - 라울 게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로소포트