JAVA

[JAVA] 스트림 API(특징, 스트림의 동작 흐름, 컬랙션과의 차이)

정공자씨 2024. 5. 1. 10:28

 

 

 

Stream API(스트림 API)

Stream API가 나오게 된 배경

  • 자바에서는 많은 양의 데이터를 저장하기 위하여 배열이나 컬랙션(Collection)을 사용하는데
  • 이렇게 저장된 데이터에 접근하기 위하여 반복문이나 반복자(iterator)를 사용하여 매번 새로운 코드를 작성해야함
  • 하지만 이렇게 작성된 코드는 길고 가독성이 떨어지며, 코드의 재사용이 거의 불가능함
  • 즉, 정형화된 처리 패턴을 가지지 못하여 데이터 종류에 따라 다른 방법으로 데이터를 처리해야 했음

이러한 문제를 해결하기 위해 JDK8부터 Stream API을 도입함

  • Stream API는 데이터를 추상화하여 다루므로, 다양한 방식으로 저장된 데이터를 조회하고 사용하기 위한 공통적인 방법을 제공함
  • 따라서, Stream API를 이용하면, 배열이나 컬랙션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방법으로 처리할 수 있음
[ JDK8 ]
- 자바는 객체 지향 언어이기 때문에, 기본적으로 함수형 프로그래밍이 불가능하였으나
- JDK8 버전부터 Stream, 람다 표현식, 함수형 인터페이스 등을 지원하면서, 자바에서 함수형으로 프로그램을 할 수 있는 API를 제공

 

 

개념

  • 자바를 이용해 함수형으로 프로그래밍 할 수 있는 API를 제공해주는데
  • 주로, 람다식을 활용하여 배열과 컬랙션 등의 데이터를 함수형으로 간단하게 처리할 수 있게 함
  • 데이터의 종류와 상관 없이 같은 방식으로 데이터를 처리할 수 있도록 하는 함수들을 정의해 두었음

 

예제

1. 배열과 리스트 데이터를 정렬(sort)하여 출력

  • sort() 메서드 사용 시, 원본 데이터 자체가 변경이 됨
public class Main {
    public static void main(String[] args) {    
            // 배열
            int [] numArr = {100, 0, 2, 5000, 30};

            // List
            List<Integer> numList = Arrays.asList(numArr);

            // sort : 원본의 데이터가 직접 정렬됨
            Arrays.sort(numArr);
            Collections.sort(numList);

            // 원본 데이터가 오름차순으로 변경되어 출력
            for (int num: numArr) {
              System.out.println(num);  // 0,2,30,100,5000
            }

            for (Integer num : numList) {
              System.out.println(num);   // 0,2,30,100,5000
            }
    }
}

 

2. Stream을 사용하여, 배열과 리스트 데이터를 정렬(sort)하여 출력

  • Stream을 사용하여 
  • 원본 데이터를 변경하지 않으면서도, 정렬의 기능을 사용할 수 있고
  • 코드를 간략하게 작성할 수 있음
public class Main {
    public static void main(String[] args) {
            // 배열
            Integer [] numArr = {100, 0, 2, 5000, 30};

            // List
            List<Integer> numList = Arrays.asList(numArr);

            // 별도의 Stream을 생성(원본의 데이터와 별개)
            Stream<Integer> listStream = numList.stream();
            Stream<Integer> arrStream = Arrays.stream(numArr);

            // 생성된 Stream의 데이터를 정렬하여 출력함
            listStream.sorted().forEach(System.out::println); // 0,2,30,100,500
            arrStream.sorted().forEach(System.out::println);

            // 원본은 변경되지 않음
            for (int num: numArr) {
              System.out.println(num);  // 100,0,2,5000,0
            }

            for (Integer num : numList) {
              System.out.println(num);  // 100,0,2,5000,0
            }
    }
}

 

 

Stream API의 특징

1. 스트림은 원본의 데이터를 변경하지 않음
2. 스트림은 단 1번만 사용할 수 있음 : 한번 사용하면 재사용이 불가
3. 내부 반복(internal iteration)을 통해 작업을 수행
4. 병렬 처리가 쉬움 : parallelStream() 메서드를 통하여 손쉬운 병렬처리를 지원함
5. 기본형 스트림을 제공

 

1. 원본의 데이터를 변경하지 않음

  • 원본 데이터를 통해서, 별도의 Stream을 생성함
  • 정렬, 필터링 등의 작업은 별도의 Stream 요소에서 처리함

2.  Stream은 단 1번만 사용할 수 있음

  • 한번 사용한 Stream은 재사용이 불가능
  • Stream이 필요한 경우, 다시 생성해서 사용하여야 함

3. 내부 반복을 통하여 작업을 수행함

  • 기존에는 반복 작업을 위해 for문이나 iterator문 등의 문법을 사용하였음
    • 코드가 길어져서 가독성이 떨어짐
  • Stream에서는 이러한 반복 문법이 내부에 숨겨져 있어, 간결한 코드 작성 가능
    • forEach() 메서드에 반복문이 숨겨져 있어, 반복 작업을 내부에서 처리함
// 반복문이 forEach 함수에 숨겨져 있어, 내부적으로 반복 작업을 처리함
arrStream.sorted().forEach(System.out::println);

 

4. 기본형 스트림을 제공함

  • String<Integer> 대신에 IntStream을 제공하여, 박싱과 언박싱의 불필요한 과정을 생략할 수 있고
  • 숫자의 경우, 유용한 메서드를 제공함
    • .sum(),  .average()

 

 

Stream API의 동작 흐름 : 생성 - 중간 연산 - 최종 연산

Stream API는 3가지 단계를 걸쳐서 동작함

[ 3가지 단계 ]
1. Stream 생성하기 
2. 중간 연산 : 가공하기
3. 최종 연산 : 결과 만들기

 

1. Stream 생성하기

  • Stream 객체를 생성하는 단계
  • 배열, 컬랙션, 파일 등 모든 종류의 데이터를 가지고 Stream을 생성할 수 있음
  • Stream은 일회용이므로, 재사용이 불가능함
    • 따라서, 필요하면 Stream을 다시 생성해서 사용해야 함
원본데이터타입 변수.stream()   // Stream 생성하기

 

2. 중간 연산 : Stream 변환하기 

  • 원본의 데이터를 별도의 데이터로 변환(가공)하기 위한 중간 연산 단계
  • 어떤 객체의 Stream을 원하는 형태로 만드는 단계이고
    • 예를 들어, 배열을 오름차순으로 정렬하고 싶을 때
    • 원본 데이터와 별도로 배열 객체의 Stream을 만들고 (생성하기)
    • 이 Stream을 오름차순의 형태로 만드는 단계 (변환하기)
  • 중간 연산을 한 리턴값은 Stream이므로, 필요한 만큼 중간 연산을 연결하여 사용할 수 있음
    • 중간 연산의 결과가 Stream을 반환해야 하기 때문에, 생성된 Stream에 여러 개의 연산이 세미콜론(;) 없이 .으로 연결이 됨
    • Stream 연산이 연결되어 있는 것을 스트림 파이프 라인(Stream PipeLine)이라고 함
원본데이터타입 변수.stream()  // 생성하기
                  .sorted()  // 가공하기

 

3. 최종 연산 : 결과 만들기

  • 가공된 데이터로부터 원하는 결과를 만들기 위한 최종 연산
  • 1번만 처리가 가능함
원본데이터타입 변수.stream()  // 생성하기
                  .sorted()  // 가공하기(오름차순하여)
                  .sum()     // 결과만들기(총합을 구하기)

 

 

예제

public class StreamTest {
    public static void main(String[] args) {
       
        // 원본데이터: List
        List<String> fruits = Arrays.asList(
                                           "strawberry", "apple", "orange", "banana");
        // Stream 연산
        fruits
            .stream()                   // Stream 생성
            .map(String::toUpperCase)   // 각 요소를 대문자로
            .sorted()                   // 오름차순 정렬
            .forEach(System.out::println);  // 스트림 돌면서 출력
    }	
}

 

 

 

 

 

Stream과 Collection의 차이점

Stream API Collection
내부 반복을 통해 작업을 수행
(사용자가 반복문을 처리할 필요가 없음)
외부 반복을 통해 작업
for-each을 통해 반복문을 만들어 연산을 처리
재사용이 불가
(단 1번만 사용할 수 있음)
재사용이 가능
요청할 때만 요소를 계산하는 고정된 자료구조
(Stream에 요소를 추가하거나 삭제하는 것이 불가)
Colletion에 포함되기 전에 각 요소들은
계산이 완료 되어 있어야 함

 

 

 

 

 

 

 

 

 

출처

 

 

[Java] 스트림(Stream) 정리

자바로 배열 또는 컬렉션 객체를 다룰 때 IDE의 추천 메소드에는 stream()이 항상 있었다. 하지만 for문이나 향상된 for문으로도 충분히 원하는 결과를 이끌어낼 수 있어서 매번 지나쳤던 기억이 있

velog.io

 

[Java] Stream API에 대한 이해 - (1/5)

1. Stream API에 대한 이해 [ Stream API에 대한 소개 ] Java는 객체지향 언어이기 때문에 기본적으로 함수형 프로그래밍이 불가능하다. (함수형 프로그래밍에 대해 이해가 부족하다면 이 글을 참고하길

mangkyu.tistory.com