Java & Spring Boot

(Java) .stream() 동작 원리 및 용도

Accept 2024. 2. 12. 22:18

 

 

해당 포스트는 JAVA의 .stream()에 대한 동작 원리와 용도에 대한 내용을 포함하고 있습니다.
.stream()을 자주 사용하지만 해당 메소드의 동작 원리에 대한 막연한 궁금증이 생겨 글을 작성하게되었습니다.

 

 

동작 원리

1. 스트림 생성 : 스트림은 Collection 인터페이스의 stream() 메소드 호출, Arrays.stream(T array), 또는 Stream 인터페이스의 of(), generate(), iterate() 등의 메소드를 통해 생성됩니다. 스트림 생성 시 데이터 소스는 변경되지 않으며, 데이터의 추상적인 뷰만을 제공합니다.

2. 중간 연산(Intermediate Operations) : 필터링(filtering), 매핑(mapping), 정렬(sorting) 등 데이터 스트림을 변환하는 연산들입니다. 중간 연산은 게으른(lazy) 특성을 가지며, 최종 연산이 호출될 때까지 실행되지 않습니다. 중간 연산은 스트림 자체를 반환하므로, 연속적으로 연산을 체이닝할 수 있습니다.

3. 최종 연산(Terminal Operations) : 스트림 처리를 완료하고 결과를 도출하는 연산입니다. 예를 들어 forEach, collect, reduce, sum, min, max 등이 있습니다. 최종 연산을 실행하면 스트림 파이프라인은 소비되어 재사용할 수 없습니다.

 

주된 사용 용도

  • 데이터 필터링 : 조건에 맞는 요소만 추출할 때 사용합니다. 예를 들어, 특정 조건을 만족하는 항목만을 리스트에서 추출할 수 있습니다.
  • 데이터 변환 : 원본 데이터를 다른 형태로 변환할 때 사용합니다. 예를 들어, 객체의 리스트에서 특정 필드만을 추출하여 새로운 리스트를 생성할 수 있습니다.
  • 데이터 집계 : 데이터의 합계, 평균, 최대, 최소 등을 계산할 때 사용합니다.
  • 데이터 정렬 : 스트림의 요소를 정렬할 때 사용합니다. 자연 순서나 커스텀 정렬 기준을 사용할 수 있습니다.
  • 데이터 수집 : 스트림의 결과를 다양한 형태로 수집할 때 사용합니다. 예를 들어, 리스트, 세트, 맵 등의 컬렉션으로 결과를 수집할 수 있습니다.

 

정리

Stream API는 데이터를 효율적으로 처리하고, 가독성 좋은 코드를 작성할 수 있게 해 주며, 병렬 처리를 통해 성능을 향상시킬 수 있는 기능을 제공합니다. 함수형 프로그래밍 방식을 적극적으로 활용하여, 보다 선언적이고 간결한 코드로 복잡한 데이터 처리 작업을 수행할 수 있게 해줍니다.

 

 

 

최종 연산이 실행되기 전까지 중간 연산이 실행되지 않는다는 것에 대한 의미

여기서 말하는 "실행되지 않는다"는 것은 중간 연산들이 실제로 데이터에 대해 작업을 수행하지 않는다는 뜻입니다. 중간 연산들은 최종 연산이 호출될 때까지 실행을 "지연"합니다. 즉, 중간 연산들은 최종 연산이 호출될 때까지는 그 연산이 적용될 실제 데이터를 처리하지 않고, 연산을 정의하는 단계에 머물러 있습니다. 

이 과정을 좀 더 구체적으로 설명하자면 아래와 같습니다.

1. 중간 연산 정의 : 코드 상에서 중간 연산 메소드를 호출하면, 해당 연산을 수행하기 위한 설정이나 조건을 정의하게 됩니다. 이때 데이터에 대한 실제 처리는 발생하지 않습니다. 예를 들어, filter 또는 map 같은 중간 연산을 정의해도, 그 시점에서는 데이터에 대한 필터링이나 매핑이 실제로 수행되지 않습니다.

2. 최종 연산 호출 : 최종 연산이 호출되는 순간, 스트림 파이프라인이 "활성화"됩니다. 이때 중간 연산들이 순서대로 실행되며, 각 중간 연산은 스트림을 통해 전달되는 데이터에 대해 실제 작업을 수행합니다. 그 후, 최종 연산이 데이터 처리를 마무리하고 결과를 반환합니다.

List<String> filteredList = list.stream()  // 스트림 생성
                                .filter(s -> s.startsWith("A"))  // 중간 연산 (실행 지연)
                                .collect(Collectors.toList());  // 최종 연산 (실제 데이터 처리 발생)



위 코드에서 filter는 최종 연산인 collect가 호출될 때까지 실제로 실행되지 않습니다. collect가 호출되는 순간, filter를 포함한 모든 중간 연산이 데이터 스트림에 대해 순차적으로 처리를 시작합니다.

이런 방식은 필요한 데이터 처리를 효율적으로 구성할 수 있게 해 줍니다. 최종 연산이 실행되기 전까지는 실제 데이터 처리가 시작되지 않으며, 이는 불필요한 데이터 처리를 줄이고, 필요한 처리만을 최적의 순서로 수행하게 만듭니다.