[JAVA] 스트림(Stream)이란? (개념, 메서드 정리)

728x90

안녕하세요. 오늘은 자바의 스트림에 대해서 알아보도록 하겠습니다.

저는 요즘 알고리즘을 공부하고 있는데요, 사람들이 짠 알고리즘 코드를 보면 스트림을 굉장히 많이 쓰고 있는 것을 볼 수 있더라구요. 뭔진 모르겠지만 저렇게 간결하고 멋진 코드를 나도 짜고 싶다! 생각하여ㅎㅎㅎ 정리해보았습니다.

 

 

스트림(Stream)이란?

  • 자바에서 데이터를 쉽게 다루기 위해 제공하는 데이터 처리 흐름
  • 배열, 리스트 같은 데이터를 일일이 반복문으로 다루기보다는 스트림을 사용해 데이터를 변환, 필터링, 합산 등의 작업을 간단하게 할 수 있음

 

 

스트림 핵심 개념

1. 데이터 흐름

  • 데이터를 처리하는 흐름을 제공하여 데이터 자체가 아니라 데이터의 흐름을 처리함

2. 변경 불가능

  • 스트림을 이용해 데이터를 가공하더라도 원본 데이터를 변경되지 않음. 가공된 결과는 새로운 데이터로 만들어짐

3. 지연 연산(Lazy Evaluation)

  • 스트림의 중간 연산(변환, 필터링 등)은 실제로 최종 연산이 호출될 때까지 수행되지 않으므로 필요 없는 연산을 피할 수 있음

4. 재사용 불가

  • 한 번 사용한 스트림은 다시 사용할 수 없으며, 재사용하려면 새로 생성해야 함

 

 

스트림 사용의 장점

  • 간결한 코드 : 반복문 없이 데이터를 간단히 필터링, 변환, 집계할 수 있음
  • 병렬 처리 지원 : 데이터를 병렬로 처리할 수 있어 성능을 높임
  • 가독성 향상 : 코드가 일긱 쉽고 직관적임

 

 

스트림의 3가지 주요 단계

1. 스트림 생성

배열/컬렉션에서 생성

  • Arrays.stream() - 배열을 스트림으로 변환
int[] numbers = {1, 2, 3, 4};
Arrays.stream(numbers); // int 배열을 IntStream으로 변환

 

  • list.stream() - List나 컬렉션을 스트림으로 변환
List<String> cats = List.of("뭉이", "조이");
cats.stream(); // List를 Stream으로 변환

 

  • String.chars() - 문자열을 각 문자의 아스키 코드 값으로 구성된 IntStream으로 변환
"abcd".chars(); // 아스키 코드 값(97, 98, 99, 100)으로 구성된 IntStream 반환

 

특수 생성 메서드

  • Stream.of() - 전달된 값을 스트림으로 만듦
Stream.of("a", "b", "c"); // Stream<String>

 

  • Stream.iterate(초기값, 조건, 연산) - 초기값에서 시작해 조건을 만족할 때까지 반복적으로 연산하여 요소를 생성
Stream.iterate(1, n -> n < 10, n -> n + 2); // 1, 3, 5, 7, 9

 

  • IntStream.range(시작값, 끝값) - 시작값부터 끝값-1까지의 정수로 스트림을 생성
IntStream.range(1, 5); // 1, 2, 3, 4

 

  • IntStream.rangeClosed(시작값, 끝값) - 시작값부터 끝값까지 포함하여 스트림 생성
IntStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5

 

 

2. 중간 연산

필터링 및 변환

  • filter(조건) - 특정 조건에 맞는 요소만 필터링
Arrays.stream(numbers).filter(n -> n % 2 == 0); // 짝수만 남김

 

  • map(변환) - 스트림의 각 요소를 다른 값으로 변환(기본형 스트림 유지)
Arrays.stream(numbers).map(n -> n * 2); // 각 숫자를 두 배로 변환

 

  • mapToObj(변환) - 기본형 스트림을 객체 스트림으로 변환하면서 요소를 변환
"abcd".chars().mapToObj(c -> (char) c); // int 값을 char로 변환하여 Stream<Character> 반환

 

  • boxed() - 기본형 스트림을 객체 스트림으로 변환
List<Integer> list = IntStream.of(1, 2, 3).boxed().toList(); // int를 Integer로 변환하여 리스트로 수집

 

  • distinct() - 중복된 요소를 제거
Arrays.stream(new int[]{1, 2, 2, 3}).distinct(); // 1, 2, 3

 

  • sorted() - 요소를 정렬
Arrays.stream(numbers).sorted(); // 오름차순 정렬

 

조건확인

  • anyMatch(조건) - 조건에 맞는 요소가 하나라도 있는지 확인함. 맞는 요소가 있으면 true 반환
Arrays.stream(new int[]{1, 2, 3}).anyMatch(n -> n > 2); // true

 

  • allMatch(조건) - 모든 요소가 조건에 맞는지 확인. 모두 만족하면 true 반환
Arrays.stream(new int[]{2, 4, 6}).allMatch(n -> n % 2 == 0); // true

 

  • noneMatch(조건) - 모든 요소가 조건에 맞지 않는지 확인. 조건에 맞는 요소가 없으면 true
Arrays.stream(new int[]{1, 2, 3}).noneMatch(n -> n > 5); // true

 

 

3. 최종 연산

결과 수집

  • collect() - 스트림을 리스트, 집합, 문자열 등의 결과로 수집
  • Collectors.toList() - List로 변환
Arrays.stream(numbers).boxed().collect(Collectors.toList());

 

  • Collectors.joining() - 문자열 스트림을 하나의 문자열로 합침
Stream.of("a", "b", "c").collect(Collectors.joining()); // "abc"

 

  • toArray() - 스트림을 배열로 변환
Arrays.stream(numbers).filter(n -> n > 2).toArray();

 

  • reduce(초기값, 연산) - 모든 요소를 하나의 값으로 합침
Arrays.stream(numbers).reduce((a, b) -> a + b); // 전체 합산

 

  • sum() : 기본형 스트림(숫자 스트림 : IntStream, LongStream, DoubleStream)에서 모든 요소의 합을 구함
Arrays.stream(numbers).sum(); // 전체 합계

 

요소 탐색

  • findFirst() - 스트림의 첫 번째 요소를 반환(*Optional로 반환)

※ Optional - 값이 있을 수도 있고 없을 수도 있는 상태를 표현하는 특별한 객체

Arrays.stream(new int[]{3, 5, 7}).findFirst().orElse(-1); // 3

 

  • findAny() - 스트림에서 아무 요소 하나를 반환. 주로 병렬 스트림에서 사용됨
Arrays.stream(new int[]{3, 5, 7}).findAny().orElse(-1); // 3 (병렬 처리 시 불특정)

 

집계 연산

  • count() - 스트림의 요소 개수 반환
Arrays.stream(new int[]{1, 2, 3, 4}).count(); // 4

 

  • average() - 모든 요소의 평균값 계산하고 Optional로 반환
Arrays.stream(new int[]{1, 2, 3, 4, 5}).average().orElse(0); // 3.0

 

  • max() / min() - 스트림의 최대값, 최소값을 반환. Optional로 반환되므로 값이 없을 때 대비해야함
Arrays.stream(new int[]{1, 2, 3}).max().getAsInt(); // 3
Arrays.stream(new int[]{1, 2, 3}).min().orElse(-1); // 1

 

병렬 처리

  • parallel() - 스트림을 병렬로 처리. 데이터가 많을 때 빠르게 계산하지만 작은 데이터에서는 오히려 성능이 떨어짐
Arrays.stream(new int[]{1, 2, 3, 4, 5}).parallel().sum(); // 병렬로 합쳐 계산
  • 순차 처리 : parallel()을 사용X -> 1 + 2 + 3 + 4 + 5를 한 코어가 순서대로 처리
  • 병렬 처리 : parallel()을 사용O -> [1, 2]와 [3, 4, 5]로 나뉘어 여러 코어가 동시에 더한 다음 합침

 

 

 

 

728x90