17. 스트림

ezi·2024년 1월 17일

스트림이란?

컬렉션 및 배열에 저장된 요소를 반복 처리하기 위해서는 for 문 이나 Iterator를 이용했다.
List - for, Set - Iterator

  • Stream과 Iterator 다른 점
    내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다.
    람다식으로 다양한 요소 처리를 정의할 수 있다.
    중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.

내부 반복자

for문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데, 이것을 외부 반복자라고 한다.
반면 스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리하는데 이것을 내부 반복자라고 한다.

외부 반복자는 컬렉션의 요소를 외부로 가져오고 처리하는 코드를 모두 개발자 코드가 가지고 있어야 한다.
내부 반복자는 개발자 코드에서 제공한 데이터 처리 코드(람다식)을 가지고 컬렉션 내부에서 요소를 반복 처리한다.

내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.

중간 처리와 최종 처리

스트림은 하나 이상 연결될 수 있다.
컬렉션의 오리지널 스트림 뒤에 필러팅 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결될 수 있다. 이와 같이 스트림이 연결되어 있는 것을 스트림 파이프라인이라고 한다.

오리지널 스트림 -( 중간 스트림 )- 집계 처리
중간 스트림들은 최종 처리를 위해 요소를 걸러내거나(필터링), 요소를 변환시키거나(매핑), 정렬하는 작업을 수행한다.

최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계 작업을 수행한다.

  • 메서드 chaining
    사슬에 이어진 고리처럼 함수를 호출할때 객체를 반환하면, 객체안에 있는 메서드를 줄줄이 이어서 호출을 할수 있는 패턴을 말한다.

스트림 파이프라인으로 구성할 때 주의할 점은 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다는 것
최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않는다.

리소스로부터 스트림 얻기

부모 : BaseStream
자식 : Stream(객체 요소 처리), IntStream, LongStream, DoubleStream

Collection 인터페이스는 스트림과 parallelStream() 메소드를 갖고 있기 대문에 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.

Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.

요소 걸러내기(필터링)

필터링은 요소를 걸러내는 중간 처리 기능이다.
distinct()-중복제거
filter()-조건 필터링, 매개타입은 요소 타입에 따른 함수형 인터페이스이므로 람다식으로 작성 가능

distinct - 객체 스트림일 경우, equals() 메소드의 리턴값이 true 면 동일한 요소로 판단한다. int,long,double스트림은 같은 값일 경우 중복 제거한다.

filter - 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링한다.

요소 변환 (매핑)

매핑은 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능이다.
mapXxx() : 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.

요소를 복수 개의 요소로 변환

flatMapXxx() : 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.

요소 정렬

Comparable 구현 객체의 정렬

스트림의 요소가 객체일 경우 객체가 Comparable을 구현하고 있어야만 sorted() 메소드를 사용하여 정렬할 수 있다. 그렇지 않다면 ClassCastException이 발생한다.
Integet, Double, String 타입은 모두 Comparable 인터페이스를 구현하고 있기 때문에 상관없지만,
사용자 정의 객체를 저장할 때에는 반드시 Comparable을 구현하고 있어야 한다.
Comparable 인터페이스에는 compareTo() 메소드가 정의되어 있다. 따라서 사용자 정의 클래스에서 이 메소들르 재정의해서 비교 결과를 정수값으로 리턴해야 한다.

compareTo(T o)
주어진 객체와 같으면 0을 리턴
주어진 객체보다 적으면 음수를 리턴
주어진 객체보다 크면 양수를 리턴

비교자는 Comparator 인터페이스를 구현한 객체를 말하는데, Comparator 인터페이스에는 compare() 메소드가 정의되어 있다. 비교자는 이 메소드를 재정의해서 비교 결과를 정수 값으로 리턴하면 된다.

compare(T o1, T o2)
o1과 o2가 동등하다면 0을 리턴
o1이 o2보다 앞에 오게 하려면 음수를 리턴
o1이 o2보다 뒤에 오게 하려면 양수를 리턴

내림차순으로 정렬하고 싶다면 Comparator.reverseOrder() 메소드가 리턴하는 Comparator를 매개값으로 제공하면 된다.

stream.sorted(Comparator.reverseOrder());

Comparator를 이용한 정렬

요소 객체가 Comparable을 구현하고 있지 않다면, 비교자를 제공하면 요소를 정렬시킬 수 있다.

sorted((o1, o2) ->. {...})

중괄호 안에는 o1이 o2보다 작으면 음수, 같으면 0, 크면 양수를 리턴하도록 작성하면 된다.

요소를 하나씩 처리(루핑)

루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.
peek() - 중간 처리 메소드 : 최종 처리가 뒤에 붙지 않으면 동작하지 않는다.
forEach() - 최종 처리 메소드

요소 조건 만족 여부(매칭)

매칭은 요소들이 특정 조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.
allMatch, anyMatch, noneMatch

요소 기본 집계

집계는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균 값, 최대값, 최소값 등과 같이 하나의 값으로 산출되는 것을 말한다. 즉 대량의 데이터를 가공해서 하나의 값으로 축소하는 리덕션이라고 볼 수 있다.

####Optional 클래스
단수히 집계값만 저장하는 것이 아니라, 집계 값이 존재하지 않을 경우 디폴트 값을 설정하거나 집계 값을 처리하는 Consumer를 등록할 수 있다.

컬렉션의 요소는 동적으로 추가되는 경우가 많다.
만약 컬렉션에 요소가 존재하지 않으면 집계 값을 산출할 수 없으므로 NoSuchElementException 예외가 발생한다.
1) isPresent() 메소드가 true를 리턴할 때만 집계값을 얻는다.
2) orElse() 메소드로 집계값이 없는 경우를 대비해서 디폴트 값을 정해놓는다.
3) ifPresent() 메소드로 집계값이 있을 경우에만 동작하는 Consumer 람다식을 제공한다.

요소 커스텀 집계

다양한 집계 결과물을 만들 수 있도록 reduce() 메소드도 제공한다.
매개값인 BinaryOperator는 함수형 인터페이스이다. 두 개의 매개값을 받아 하나의 값을 리턴하는 apply() 메소드를 가지고 있기 때문에 (a,b) -> {... return 값;} 람다식을 작성할 수 있다.
reduece는 스트림에 요소가 없을 경우 예외가 발생하지만, identity 매개값이 주어지면 이 값을 디폴트 값으로 리턴한다.

요소 수집

스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다. 이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 있고, 요소들을 그룹핑한 후에 집계도 할 수 있다.

자바16부터는 좀 더 편리하게 요소 스트림에서 List 컬렉션을 얻을 수 있다. 스트림에서 바로 toList();를 사용하면 된다.

요소 그룹핑

collect() 메소드는 단순히 요소를 수집하는 기능 이외에 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공한다.
Collectors.groupingBy() 메소드에서 얻은 Collector를 collect() 메소드를 호출할 때 제공하면 된다.

groupingBy(Function<T, K> classifier)

groupingBy()는 Function을 이용해서 T를 K로 매핑하고, K를 키로 해 List<'T>를 값으로 갖는 Map 컬렉션을 생성한다.

https://gem1n1.tistory.com/159

요소 병렬 처리

요소 병렬 처리란 멀티코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것을 말한다.

동시성과 병렬성

멀티스레드는 항상 동시성 또는 병렬성으로 실행된다.

  • 동시성 : 멀티 작업을 위해 멀티 스레드가 하나의 코어에서 번갈아 가며 실행하는 것
    한 시점에 하나의 작업만 진행

  • 병렬성 : 멀티 작업을 위해 멀티 코어를 각각 이용해서 병렬로 실행하는 것
    한 시점에 여러개의 작업을 병렬로 실행

  • 데이터 병렬성 : 전체 데이터를 분할해서 서브 데이터셋으로 만들고 이 서브 데이터셋들을 병렬처리해서 ㅂ작업을 빨리 끝내는 것 > 자바 병렬 스트림은 데이터 병렬성을 구현한 것

  • 작업 병렬성 : 서로 다른 작업을 병렬 처리하는 것. 서버는 클라이언트에서 요청한 내용을 개별 스레드에 병렬로 처리한다.

포크조인 프레임워크

자바 병렬 스트림은 요소들을 병렬 처리하기 위해 포크조인 프레임워크를 사용한다.
포크 단계에서 전체 요소들을 서브 요소셋으로 분할하고, 각각의 서브 요소셋을 멀티 코어에서 병렬로 처리한다. 조인 단계에서는 서브 결과를 결합해서 최종 결과를 만든다.

포크조인 프레임워크는 병렬처리를 위해 스레드풀을 사용한다. 각각의 코어에서 서브 요소셋을 처리하는 것은 작업 스레드가 해야 하므로 스레드 관리가 필요하다. 요소셋 수만큼 스레드 생성

병렬 스트림 사용

백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬처리를 할 수 있다.
parallelStream() : 컬렉션(List,Set) 으로부터 병렬 스트림을 바로 리턴한다.
parallel() : 기존 스트림을 병렬 처리 스트림으로 변환한다.

ArrayList와 배열은 인덱스로 요소를 관리

profile
차곡차곡

0개의 댓글