item 45) 스트림은 주의해서 사용하라

스트림 파이프라인

  • 스트림(stream)

    • 데이터 원소의 유한 혹은 무한 시퀀스를 의미

    • 컬렉션, 배열, 파일, 정규표현식 패턴 매치, 난수 생성기, 혹은 다른 스트림 등에서 데이터 원소들을 가져온다.

  • 스트림 파이프라인은 스트림 원소들로 수행할 수 있는 연산 단계를 표현한다.

  • 소스 스트림 -> (하나 이상의 중간연산) -> 종단연산 으로 구성된다.

  • 종단 연산: 마지막 중간 연산이 반환한 스트림에 최후의 연산을 수행한다. 대표적으론 원소를 정렬해 컬렉션에 담거나, 특정 원소 하나를 선택하거나, 모든 원소를 출력하는 등의 연산을 한다. 스트림 파이프라인에 필수적이다.

  • 지연 평가(lazy evaluation): 계속해서 합쳐진 중간연산들을 한번에 최종 연산으로 처리하며, 평가는 종단 연산이 호출될 때 이루어진다.

  • 메서드 연쇄를 제공하여 파이프라인 하나를 구성하는 모든 호출을 연결해 단 하나의 표현식으로 완성할 수 있는 fluent API이다.

  • 기본적으로 순차적 수행이며, parallel 메서드를 통해 병렬 실행 가능하지만 효과를 보긴 어렵다 (item48)

스트림 사용하기

  • 스트림 변수의 이름을 지어 스트림 안의 원소가 어떤 것인지 명확히 하면 좋다.

  • 기본 타입인 char용 스트림을 지원하지 않으므로 char 값들을 처리할 때는 스트림을 사용하지 말고 메서드를 정의해 사용하자.

  • chars() 메서드는 int 원소들을 반환한다.

  • 코드 가독성과 유지보수 측면에서 손해볼 수 있으므로 스트림과 반복문을 적절히 조합하는 것이 최선이다.

  • 기존 코드는 스트림을 사용하도록 리팩토링 하되, 새 코드가 나아보일 때에만 반영한다.

  • 스트림 파이프라인은 되풀이되는 계산을 함수 객체(람다/메서드참조)로 표현한다.

코드 블록 vs 람다

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

  • 코드 블록은 return 문을 사용해 메서드에서 빠져나가거나, break, continue문 사용하거나, 메서드 선언에 명시된 검사 예외를 던질 수 있다. 람다에서는 불가능하다.

스트림의 특징

  • 스트림은 원소의 시퀀스를 일관되게 변환하거나, 필터링하거나, 하나의 연산으로 결합하거나, 컬렉션에 모으거나, 특정 조건 검사할 때 적합하다.

  • 다음 단계로 넘어갈 때 원래 값을 버리고 새로운 값만 가지므로, 파이프라인의 여러 단계들에서의 값들에 대한 동시 접근이 어렵다.

  • 앞 단계의 값이 필요할 때 거꾸로 매핑해 값을 구해내는 방식이 가능하다면 이 방식을 사용하도록 한다.

  • 스트림을 반환하는 메서드 이름은 원소의 정체를 알려주는 복수 명사로 사용하는 게 좋다.

  • 아래는 suit 하나 당 Rank의 value마다 새로운 Card 객체를 만들어내어 collect하는 예제이다.

flatMap: 스트림의 원소 각각을 하나의 스트림으로 매핑한 후 다시 하나의 스트림으로 합치는 평탄화 과정

private static List<Card> newDeck() {
    return Stream.of(Suit.values())
            .flatMap(suit ->
                    Stream.of(Rank.values())
                            .map(rank -> new Card(suit, rank)))
            .collect(toList());
}
  • 결국, 반복 방식과 스트림을 조합했을 때 가장 효과적이다.

  • 두 방식 중 어떤게 나은지 확인하기 위해서는 둘 다 사용해보고 선택하는게 좋다.

Last updated