45. 주의해서 스트림을 사용하자.

Joshua Bloch 저 「Effective Java 3rd Edition, 2018」를 읽고 정리하였습니다.

추가 참고 자료

  • 블로그 1arrow-up-right - 기본

    • 스트림은 '데이터의 흐름’

    • 배열 또는 컬렉션 (Collection, List, Set) 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다.

    • 연산 종류

      • Intermediate operation (중간 연산)

        • Filtering / Mapping / Sorting / Iterating

        • chaining 가능

          • map() - 스트림 내 요소들을 하나씩 특정 값으로 변환

          • flatMap() - 스트림 내 요소들을 단일 컬렉션으로

      • Terminal operation (종단 연산)

        • Calculating / Reduction / Collecting / Matching / Iterating

    • 스트림 소비는 한번만 가능arrow-up-right

      • Stream<String> wordsConnectionWithDuplication = words.stream().flatMap((String line) -> Arrays.stream(line.split("")));
        wordsConnectionWithDuplication.forEach(System.out::println);
        Stream<String> wordsConnectionWithoutDuplication = wordsConnectionWithDuplication.distinct();
        wordsConnectionWithoutDuplication.forEach(System.out::println);
    • 기본 타입형

      • IntStream, LongStream, DoubleStream

    • 여러 쓰레드 이용하여 간단하게 병렬처리

스트림 예제

1 칼로리순으로 정렬 2 칼로리순으로 정렬 + 5개만 3 칼로리순으로 정렬 + 5개만 + 채식주의자를 위한 음식만 + 500 칼로리 보다 낮은 음식 4 이름들만을 뽑아내어 리스트 만들기 5 모든 메뉴가 1000 칼로리를 넘지 않음을 확인 6 모든 칼로리의 합 7 채식주의자 메뉴가 하나라도 있으면 true 8 칼로리 중 최대값, 값이 없을 경우 대비하기

책으로 돌아와서

  • 스트림 API는 병렬과 순차적인 벌크연산을 수행하기 위해 자바 8에 도입이 되었다.

  • 주요한 두가지의 추상화 개념 1. 데이타 원소(elements)들의 유한과 무한 시퀀스을 표현하는 스트림 2. 이러한 원소들의 multistage 계산을 표현하기 위한 스트림 파이프라인

  • 스트림의 원소들은 어디에서든 올수 있는데 대표적으로 Collection, Array, File, RegEx (matcher), 난수 생성기, 혹은 다른 스트림이 있다.

  • 파이프라인을 병렬로 실행하려면, 파이프라인을 구성하는 스트림 중 하나에서 parallel 메서드를 호출해주기만 하면 된다. (다만, 효과를 볼 수 있는 상황은 많지 않은데, 아이템 48arrow-up-right]을 참고하자.)

  • 스트림 안의 데이터 원소들은 객체 참조나 기본 타입 값이다. (기본타입 - int, long, double)

  • Lazy evaluation

    • 종단 연산이 호출 없으면 아무일 하지 않음

  • 주의할것

    • 언제사용?

      • 스트림을 쓰면 짧고 깔끔해질 수 있지만, 때때로 읽기 어렵고 유지보수도 힘들어진다.

      • 기존 코드를 스트림으로 리팩토링하여보고, 더 이쁠때만 사용하자

    • 네이밍

      • 스트림을 반환하는 메서드는 복수형를 쓰자, 예, primes()

스트림 없는 아나그램 예제

텍스트 파일에서 단어들을 꺼내와, 아나그램 그룹들로 만드는 코드

과한 스트림을 사용한 아나그램 예제

alphabetize 메서드도 스트림으로 만들어 버린, 이해하기가 어려운 코드

적절한 스트림 사용 예제

char 는 스트림을 이용하지 않는게 낫다

올바르게 사용하려면 아래처럼 형변환을 명시적으로 해줘야 한다.

성능이 느려질 수도 있기 때문에 char 값들을 처리할 때는 스트림을 삼가는 편이 낫다.

반복문 vs 스트림

반복문을 선택해야 될때

  • 코드 블록에서는 범위 안의 지역변수를 읽고 수정할 수 있지만 람다에서는 final 이거나 사실상 **final**인 변수만 읽을 수 있고, 지역변수를 수정하는 건 불가능하다.

  • 코드 블록에서는 return 문을 사용해 메서드를 빠져나가거나, **break**나 **continue**문으로 블록 바깥의 반복문을 종료하거나 반복을 건너뛸수 있다. 또한, 메서드 선언에 명시된 검사 예외를 던질 수 있다.

스트림을 선택해야 될때

  • 원소들의 시퀀스를 일관되게 변환 할 때

  • 원소들의 시퀀스를 필터링 할 때

  • 원소들의 시퀀스를 공통된 속성을 기준으로 컬렉션으로 만들때

  • 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾을 때

반복문이 적절한 경우

중첩 람다(suit, rank)가 들어가 더 이해하기 어렵지만, 스트림으로 하는 경우가 더 낫다는 사람도 있을 것이다. 하지만 명확한것은 반복문으로 할때 유지보수의 가능성이 더 올라간다. (한가지 이유는, 람다를 모르는 경우 이해하기 힘들 수 있기에)

결론

  • 스트림을 반환하는 메서드는 복수형를 쓰자, 예, primes()

  • 스트림이 적절하지 않은 타입도 있다. 예, char.

  • 스트림으로 병렬 처리할 때 성능이 안좋을 수 있으니 주의하자

  • 스트림 내 람다 이름은 readabilty 에 큰 영향을 주니, 네이밍 잘하자

  • 반복문을 스트림으로 바꾸고 싶은 유혹이 들 때가 있지만, 복잡한 작업에는 스트림과 반복문을 적절히 조합하는 게 최선이다. 즉, 기존 코드는 스트림을 사용하도록 리팩터링하고 리팩토링 새 코드가 더 나아 보일 때만 반영하는게 좋다.

Last updated