Stream이란?

자바 스트림은 순서대로 적용되고 변경할 수 없는 함수 모음을 추상화한 것이다. 스트림과 컬렉션의 가장 중요한 차이점은 스트림이 데이터를 보유하지 않는다는 것이다. 데이터에 대해 작동하는 기능만 지정할 수 있으며, 스트림에서 작업을 수행하면 원본 스트림에 영향을 미치지 않는다.

스트림 구조

스트림은 데이터가 흐르는 파이프라인과 데이터에서 작동하는 기능을 나타낸다. 이 파이프라인은 스트림 소스, 0개 이상의 중간 작업, 터미널 작업으로 구성된다.

List numbers = Arrays.asList(1, 2, 3, 4);
List result = numbers.stream()
    .filter(e -> (e % 2) == 0)
    .map(e -> e * 2)
    .collect(toList());

주요 스트림 작업

Java 8 이상에서는 stream() 메서드를 호출하여 모든 컬렉션에서 스트림을 얻을 수 있다.

메서드설명
filter()조건에 맞는 요소만 포함하는 새 스트림을 반환
map()스트림 요소를 다른 것으로 변환
reduce()스트림을 단일 요소로 축소
collect()스트림을 List 같은 구체적인 컬렉션으로 변환

주의사항

공유 상태 변형 문제

// 잘못된 예시 - 동시성 문제 발생 가능
List names = new ArrayList();
people.stream()
    .filter(p -> p.getGender() == Gender.FEMALE)
    .map(Person::getName)
    .forEach(name -> names.add(name));  // 위험

스트림이 병렬인 경우 공유 상태에 요소를 동시에 추가하면 오류가 발생할 수 있다.

올바른 방법 - collect() 사용

List names = people.stream()
    .filter(p -> p.getGender() == Gender.FEMALE)
    .map(Person::getName)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

터미널 작업 누락 문제

중간 작업만 지정하고 터미널 작업이 누락되면 아무것도 실행되지 않는다. 터미널 작업이 있을 때만 중간 작업이 실행된다.

// 출력 없음 - 터미널 작업 누락
Stream.of("Cathy", "Alba", "Beth")
    .filter(s -> {
        System.out.println("filter " + s);
        return true;
    });

중간 작업과 터미널 작업

자바 스트림은 지연 평가(lazy evaluation)된다. filter, map 같은 중간 작업은 지정 시 평가되지 않고, 터미널 작업이 호출될 때 계산이 수행된다.

  • 중간 작업: Stream을 반환하는 작업 (filter, map 등)
  • 터미널 작업: Stream 이외의 것을 반환하는 작업 (forEach, collect, reduce 등)

성능 최적화

중간 작업의 순서가 성능에 영향을 준다. filter()를 map()보다 먼저 지정하면 map()이 적게 호출되어 성능이 향상된다.

키워드

  • 순서대로 적용되고 변경할 수 없는 함수들의 추상화
  • 데이터가 아닌 기능에 대해 작동
  • 주요 작업: filter, map, reduce, collect
  • 지연 평가: 터미널 작업 호출 시 실행