본문 바로가기
effective java

Item46. 스트림에서는 부작용 없는 함수를 사용하라

by mjjang 2022. 3. 6.
  • 스트림 패러다임 - 계산을 일련의 변환으로 재구성하는데 이때 각 변환 단계는 가능한 한 이전 단계의 결과를 받아 처리하는 순수함수여야 한다.
  • 순수 함수 - 입력만이 결과에 영향을 주는 함수 (다른 상태를 참조하지 않고 다른 상태를 변경하지 않음)

즉, 스트림을 올바르게 사용하려면 스트림 연산에 건네는 함수 객체는 모두 부작용이 없어야 한다.

스트림 패러다임을 잘못 이해하고 사용한 경우

public static  void main(String[] args) {
	    Map<String, Long> freq = new HashMap<>();
	    Stream<String> words = Arrays.asList("a", "b", "c", "a").stream();
	
	    words.forEach(word -> {
	        freq.merge(word, 1L, Long::sum);
	    });
	
	    System.out.println(freq); // {a=2, b=1, c=1}
}

스트림 병렬화에 대한 공식 문서의 Side-effects 항목을 참고하면, forEach 내부에 로직이 하나라도 더 추가된다면 동시성 보장이 어려워지고 가독성이 떨어질 위험이 있다.

public static  void main(String[] args) {
	    Map<String, Long> freq;
	    Stream<String> words = Arrays.asList("a", "b", "c", "a").stream();
	
	    freq = words.collect(groupingBy(String::toLowerCase, counting()));
	
	    System.out.println(freq); // {a=2, b=1, c=1}
}

foreach연산은 스트림 계산 결과를 보고할 때만 사용하고 계산하는 데는 쓰지 말자.

Collector (수집기)

스트림을 하나의 객체(collection(list, set, map)로 만드는 과정

Collectors의 대부분은 스트림을 맵으로 취합하는 기능이다.

가장 간단한 메서드는 toMap()이다.

toMap 수집기 사용 예

public static  void main(String[] args) {
      Author authorA = new Author("백기선");
      Author authorB = new Author("김영한");

      List<Book> books = Arrays.asList(
                new Book(authorA,"자바", 10)
              , new Book(authorA, "TDD", 20)
              , new Book(authorB, "JPA", 5)
              , new Book(authorB,"스프링", 7));

      Map<Author, Book> error = books.stream().collect(
              toMap(Book::getAuthor, book -> book));

      Map<Author, Book> firstBook = books.stream().collect(
              toMap(Book::getAuthor, book -> book, (prev, next) -> prev));

      Map<Author, Book> bestSeller = books.stream().collect(
              toMap(Book::getAuthor, book -> book, BinaryOperator.maxBy(comparing(Book::getSales))));

      System.out.println(firstBook);
      System.out.println(bestSeller);
}

병합 함수 BinaryOperator<U> 사용, U는 해당 Map의 값 타입

같은 키를 공유하는 값들은 병합 함수를 사용해 기존 값과 합쳐진다.

joining 사용 예

Author authorA = new Author("백기선");
Author authorB = new Author("김영한");

List<Book> books = Arrays.asList(
          new Book(authorA,"자바", 10)
        , new Book(authorA, "TDD", 20)
        , new Book(authorB, "JPA", 5)
        , new Book(authorB,"스프링", 7));

String allBooks = books.stream()
        .map(Book::getName)
        .collect(joining(", ")); // CharSequence 타입의 구분문자를 매개변수로 받음

System.out.println(allBooks); //자바, TDD, JPA, 스프링

joining은 원소들을 연결하는 수집기를 반환한다.

정리

스트림의 핵심은 부작용 없는 함수 객체에 있다.

스트림을 올바르게 사용하려면 수집기를 잘 알아야 한다.

중요한 수집기 팩터리는 toList, toSet, toMap, groupingBy, joining이다.

댓글