Java

Stream API

ryeonng 2024. 10. 1. 17:29
스트림 패키지의 구분

자바 I/O 스트림은 데이터를 읽고 쓰는 목적으로 사용된다. 파일, 네트워크, 메모리 등 다양한 소스에서 바이트나 문자 데이터를 읽거나 쓰기 위한 스트림이다. InputStreamOutputStream이 대표적인 I/O 스트림이다.

 

Stream API와 자바 I/O 스트림은 이름은 비슷하나, 완전히 다른 개념이다. Stream API는 자바 8에서 도입된 중요한 기능 중 하나로, 데이터 컬렉션(자료구조)을 처리할 때 사용되는 강력한 도구이다. 스트림(Stream)은 데이터를 선언적으로 처리할 수 있게 하며, 특히 대용량 데이터 처리 및 데이터 흐름 제어에 매우 유용하다.

 

Stream API 자바 I/O 스트림
데이터 컬렉션(List, Set, Map, 배열)을 처리 파일, 네트워크 등 외부 자원으로부터 입출력
함수형 프로그래밍 스타일로 데이터 처리 바이트/문자 단위로 데이터를 읽거나 씀
필터링, 변환, 정렬, 집계 등 데이터 가공 데이터 전송 및 저장을 위한 스트림
중간 연산과 최종 연산으로 나누어 처리 입력 스트림에서 읽고, 출력 스트림에 씀
데이터를 처리하기 위한 한 번의 흐름 (일회성) 파일 등에서 데이터를 반복적으로 읽고 씀

 

Stream API

Stream API는 컬렉션(List, Set, Map 등)이나 배열 등의 데이터 소스를 함수형 프로그래밍 스타일로 처리할 수 있게 해준다. 데이터를 필터링하고, 변환하고, 집계하는 등의 작업을 더 간결하고 효율적으로 수행할 수 있다.

 

 

특징

  • 선언적 방식 : 데이터를 어떻게 처리할 지에 집중하기 않고 무엇을 할지에만 집중하는 방식이다.
  • 내부 반복 : 컬렉션의 모든 요소에 대해 직접 반복하지 않고, 스트림이 반복을 관리하여 간결한 코드를 작성할 수 있다.
  • 일회성 : 스트림은 한 번 사용하면 재사용할 수 없다. 필요하면 다시 스트림을 생성해야 한다.
  • 지연 실행 : 스트림은 중간 연산이 완료되기 전까지 실행되지 않으며, 최종 연산이 호출될 때만 연산이 수행된다.

 

Stream의 두 가지 연산 동작 방식

 

1. 중간 연산 (Intermediate Operation) :

  • 스트림의 중간 단계에서 데이터를 변환하거나 필터링한다.
  • 여러 중간 연산을 연결할 수 있으며, 지연된 실행(lazy execution이 이루어진다.
  • 예시 : filter(), map(), sorted()

2. 최종 연산 (Terminal Operation) :

  • 스트림을 종료하고, 데이터를 처리한 후 결과를 반환한다.
  • 최종 연산이 실행되기 전까지는 중간 연산이 수행되지 않는다.
  • 예시 : forEach(), collect(), reduce()
지연 실행(lazy execution)
최종 연산이 실행되기 전까지는 중간 연산이 수행되지 않는다.

filter(); 호출
map(); 호출
sorted(); 호출
위 코드를 호출하였지만 중간 연산자들이기 때문에 수행되지 않는다.(지연 연산)
--------------------------------------------------------------------------------------------------------------
forEach();
최종 연산이 호출될 때 비로소 모든 중간 연산들이 함께 실행되며 데이터를 처리한다.

중간 연산은 준비만 하고 실행되지 않는다. 실제로 데이터가 처리되는 것은 최종 연산이 호출된 이후이다.


Stream API를 활용한 데이터 컬렉션 처리

컬렉션(자료구조) 내의 데이터를 원하는 형태로 변환하거나, 필요 없는 데이터를 걸러내고, 특정 방식으로 계산하는 작업을 뜻한다.

  • 필터링 : 조건에 맞지 않는 데이터를 걸러내는 작업 (예 : 나이가 18살 이상인 사람만 필터링)
  • 정렬 : 데이터를 오름차순 또는 내림차순으로 정렬하는 작업 (예 : 성적 순으로 학생을 정렬)
  • 변환 : 데이터를 다른 형태로 바꾸는 작업 (예 : 섭씨 온도를 화씨로 변환)
  • 집계 : 데이터를 하나의 값으로 축소하는 작업 (예 : 모든 상품의 총 합 계산)
시나리오 코드 1 - 자료구조 내의 데이터를 필터링 하기

18세 이상의 학생들만 필터링하고, 그 결과를 List로 수집하는 작업을 해보자.

중간 연산 - filter 사용

최종 연산 - collect

(Collectors는 자바 8에서 제공하는 유틸리티 클래스로, collect() 메서드와 함께 사용된다.)

package ch02;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterTest1 {
    
    public static void main(String[] args) {
        
        // 샘플 데이터 준비
        List<Integer> ages = Arrays.asList(20, 11, 18, 24, 33, 40, 2);
        
        // 나이가 18 이상인 학생만 필터링 후 List로 수집
        List<Integer> adultAges = ages.stream()
                                      .filter(age -> age >= 18) // 중간 연산: 나이 18 이상 필터링
                                      .collect(Collectors.toList()); // 최종 연산: 결과를 List로 수집
        
        System.out.println(adultAges); // 출력: [20, 18, 24, 33, 40]
    }
}

Collectors.toSet() : Collectors.toSet()은 스트림의 데이터를 Set으로 수집한다.

Collectors.toMap() : 스트림의 데이터를 Map으로 수집할 때 사용된다.

  • toMap()은 두 가지 정보를 필요(Key 값을 어떻게 지정할 지, Value 값을 어떻게 지정할 지)

 

시나리오 코드 2 - 자료구조 내의 데이터를 변환해보기

제품의 가격을 리스트로 저장한 뒤, 스트림을 활용해서 모든 제품의 가격에 10% 세일을 적용해 새로운 리스트를 생성해보자.

중간 연산 - map 사용최종 연산 - collect, forEach

package ch02;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMapTest2 {
    
    public static void main(String[] args) {
        
        List<Double> prices = Arrays.asList(100.0, 200.0, 300.0);

        // 10% 세일 적용된 가격으로 변환하고 List로 수집
        List<Double> discountedPrices = prices.stream()
                                              .map(price -> price * 0.9)  // 모든 가격에 10% 할인
                                              .collect(Collectors.toList());

        // 할인된 가격 출력 (자료구조의 스트림을 사용한 최종연산 사용 코드)
        discountedPrices.forEach(e -> System.out.println("할인 가격 : " + e));  // 출력: 90.0, 180.0, 270.0
    }
}

 

결과 확인

할인 가격 : 90.0
할인 가격 : 180.0
할인 가격 : 270.0

 

 

시나리오 코드 3 - 자료구조 내의 데이터를 집계해보기

정수 리스트에서 값을 합산하여 총합 계산을 집계해보자.

최종 연산 - reduce.reduce(초기값, 람다식)의 형태이다.주의 : reduce는 스트림의 요소들을 하나로 결합하여 단일 결과값을 생성한다. 이 과정에서 스트림의 모든 데이터를 처리하므로, 이후에 추가적인 연산이 불가능하다. 즉, 스트림을 종료하는 연산이다.

package ch02;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamReduceTest3 {
    
    public static void main(String[] args) {
        
    	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

    	// 리스트의 숫자들을 모두 더함
    	int sum = numbers.stream()
    	                 .reduce(0, (a, b) -> a + b);  // 스트림의 요소들을 하나로 결합
    	System.out.println(sum);  // 출력: 15
    }   
}

 

시나리오 코드 4 - 자료구조 내의 데이터를 정렬해보기

정수 리스트를 오름차순으로 정렬하여 출력해보자.

최종 연산 - sorted();

package ch02;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class StreamStoredTest4 {

	public static void main(String[] args) {

		List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);

		// 리스트의 숫자들을 오름차순으로 정렬
		List<Integer> sortedNumbers1 = numbers.stream()
				.sorted() // 오름차순 정렬
				.collect(Collectors.toList());

		// 내림차순 정렬
		List<Integer> sortedNumbers2 = numbers.stream()
				.sorted(Comparator.reverseOrder()) // 내림차순 정렬
				.collect(Collectors.toList());

		System.out.println(sortedNumbers1); // 출력: [1, 2, 3, 4, 5]
		System.out.println("------------"); 
		System.out.println(sortedNumbers2); // 출력: [5, 4, 3, 2, 1]

	}
}

 

'Java' 카테고리의 다른 글

래퍼 클래스  (0) 2024.10.01
GET 방식과 URL 주소 설계  (0) 2024.09.27
전략 패턴  (0) 2024.09.27
싱글 톤 패턴  (0) 2024.09.26
콜백 메서드 만들어 보기  (0) 2024.09.26