[java] Stream

happiyoung_·2024년 8월 23일

java

목록 보기
8/9

Intro

빅데이터의 등장으로 함수형 프로그래밍이 각광을 받고 있다.
함수형 프로그래밍은 멀티쓰레드가 가능하여 병렬처리가 가능하기 때문이다.
이러한 병렬처리를 해주는 자바의 기능 스트림을 소개한다.

Stream

다양한 데이터 소스 (컬랙션, 배열)를 표준화된 방법으로 다루기 위한것

기존에 List, Set, Map으로 표준화 하려 했으나 실패하고 나온 본격적인 것이다.
표준화를 하려는 이유는 뭘까?
기존에는 데이터 소스마다 중간연산의 방법 최종 결과를 내는 방법이 다 달랐다. 하지만 스트림이 등장하여 모든걸 통일할 수 있게 되었다.

스트림 변환 방법

List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream; // 컬렉션
Stream<String> strStream = Stream.of(new String[] { "a", "b", "c"}); // 배열
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0,2,4,6...
Stream<Double> randomStream = Stream.generate(Math::random); // 람다식
IntStream intStream = new Random().ints(5); // 난수 스트림(크기가 5)

스트림 작업 방식

  1. 스트림 만들기
  2. 0~n번 중간연산
    • 연산 결과가 스트림인 연산. 반복적으로 적용가능
  3. 1번의 최종연산
    • 연산 결과가 스트림이 아닌 연산. 단 한번만 적용가능 (스트림의 요소를 소모)

다음 예시를 보자.

stream.distinct().limit(5).sorted().forEach(System.out::printn)

  • distict, limit, sorted는 중간연산에 해당
  • forEach는 최종 한번만하는 연산
    풀어서 쓰면 다음과 같다.
String[] strArr = { "dd", "aaa", "CC", "cc", "b" };
Stream<String> stream = Stream.of(strArr); // 문자열 배열이 소스인 스트림
Stream<String> filteredStream = stream.filter(); // 걸러내기 (중간 연산)
Stream<String> distinctedStream = stream.distinct(); // 중복제거(중간 연산)
Stream<String> sortedStream = stream.sort(); // 정렬 (중간 연산)
Stream<String> limitedStream = stream.limit(5); // 스트림 자르기(중간연산)
int total = stream.count(); // 요소 개수 세기 (최종 연산)

스트림의 특징

1. 스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.

List<Integer> list = Arrays.asList(3,1,5,4,2);
List<Integer> sortedList = list.stream().sorted() // list를 정렬해서
							.collect(Collectors.toList()); // 새로운 list에 저장
System.out.println(list); // [3,1,5,4,2]
System.out.println(sortedList); // [1,2,3,4,5]

2. 스트림은 Iterator 처럼 일회용이다. (필요하면 다시 스트림을 생성해야한다.)

strStream.forEach(System.out::println); // 모든 요소를 화면에 출력(최종연산)
int numOfStr = strStream.count(); // 에러, 스트림이 이미 닫혔음.

3. 최종 연산 전까지 중간 연산이 수행되지 않는다. - 지연된 연산

IntStream intStream = new Random().ints(1,46); // 1~45 범위의 무한 스트림
intStream.distinct().limit(6).sorted() // 중간 연산
			.forEach(i -> System.out.println(i + ",")); // 최종 연산

무한 스트림이라고 생각하면 어떻게 중복을 제거하고 6개를 자르나 라는 생각이 들수도 있으나 스트림은 지연된 연산을 진행하기 때문에 일단 중간 연산에 필요한 모든걸 고려해두고 필요할때 불러서 처리한다.

4. 스트림은 작업을 내부 반복으로 처리한다.

for(String str : strList)
	System.out.println(str);

위의 반복문을 다음과 같이 편리하게 처리가능하다.
stream.forEach(System.out::println);
이게 가능한 이유는 메서드안에 기능이 포함되어 있기 때문이다.

void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action); // 매개변수의 널 체크
    
    for(T t : src) // 내부 반복
    	action.accept(T);
}

5. 스트림의 작업을 병렬로 처리 - 병렬스트림

Stream<String> strStream = Stream.of("dd","aaa", "CC", "cc", "b");
int sum = strStream.parallel() // 병렬 스트림으로 전환 (속성만 변경)
				.mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합
  • 반대로의 변환은 sequential() 이용

6. 기본형 스트림 - IntStream, LongStream, DoubleStream

  • 오토박싱 & 언박싱의 비효율이 제거된다.(Stream<Integer> 대신 IntStream 사용)

    오토박싱 : 기본형을 참조형으로 바꾸는 과정
    (예시) 1 (기본형) -> new Integer(1) (참조형)

Stream<T> 의 경우, T에는 기본형이 들어갈수 없고 참조형만 가능하다. 그래서 오토박싱 언박싱 과정이 불가피한데 이는 시간을 많이 잡아먹는 작업이다. 이러한 비효율을 제공하기 위해 기본형 스트림을 제공하고 있다.

  • 숫자와 관련된 유용한 메서드를 Stream<T> 보다 더 많이 제공

Outro

영어단어 스트림을 번역하면 어떤 흐름을 의미한다. 스트림은 데이터 모음을 변환하며 생성되고 데이터들에 한해 어떤 중간 연산과정을 거치게하고 최종 단한번의 연산으로 결과를 만들어내고 소멸된다.
스트림의 등장으로 빅데이터의 빠른 병렬처리가 가능해졌다는 것에 의의를 두고자 한다.

profile
해삐한 다영의 컴퓨터와 친해지기 프로젝트 🥰

0개의 댓글