온라인 학습/더 자바, Java 8 강의

3. Stream

2022. 11. 10. 16:21

<1> Stream 소개


Stream

  • sequence of elements supporting sequential and parallel aggregate operations
  • 연속된 데이터를 처리하는 오퍼레이션의 모음이라고 볼 수 있다.
  • 데이터를 담고 있는 저장소(컬렉션)가 아니다.
  • 기본적으로 functional 하다. 스트림이 처리하는 데이터 소스를 변경하지 않는다.

또 다른 스트림이 되는 것이기 때문에 스트림으로 전달받은 데이터 자체를 대문자로 바꾸지 않는다.

출력해보면 그대로 소문자로 나온다.

 

  • 스트림으로 처리하는 데이터는 오직 한번만 처리한다.
  • 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.)
  • 중개 오퍼레이션은 근본적으로 lazy 하다.

스트림 뒤에 사용하는 여러 메소드들이 있는데, 크게 중개 오퍼레이터(중개)터미널 오퍼레이터(종료) 두 가지로 나눌 수 있다.

 

출력 안 됨

중개형 오퍼레이터는 터미널 오퍼레이터가 오기 전까지 실행하지 않는다.

map은 중개 오퍼레이터이고 정의만 한 상태이므로 출력 코드가 실행되지 않았다.

 

터미널 오퍼레이터 사용
출력됨

터미널 오퍼레이터 붙여주면 이제 출력이 실행된다. (콜렉터에는 대문자로 바뀐 게 안 보인다.)

 

이렇게 하면 대문자로 바뀐 것 볼 수 있다.

그 다음 출력 보면 원본 데이터 소스 자체는 여전히 바뀌지 않았다는 것도 확인할 수 있다.

 

  • 손쉽게 병렬 처리할 수 있다.

위와 같이 하면 루프를 돌며 각 엘리먼트에 대한 작업을 하는데, 이런 작업을 병렬적으로 처리하기가 어렵다.

 

병렬 처리

parallelStream : 병렬 스트림 생성

parallelStream은 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 chunk 단위로 분할한 스트림이다. 기본적으로 ForkJoinPool을 사용한다.

병렬 처리는 결국 여러 스레드가 작업을 분할받아, 처리한 결과를 다시 합치는 일이기 때문에 병렬 처리가 항상 빠른 것은 아니다

 

 

스트림 파이프라인

  • 0 또는 다수중개 오퍼레이션1개종료 오퍼레이션으로 구성한다.
  • 스트림의 데이터 소스는 오직 터미널 오퍼네이션을 실행할 때에만 처리한다.

 

 

중개 오퍼레이션 (intermediate operation)

  • Stream을 리턴한다.
  • Stateless / Stateful 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted 처럼 이전 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Stateful 오퍼레이션이다.)
  • filter, map, limit, skip, sorted, ...

 

 

종료 오퍼레이션 (terminal operation)

  • Stream을 리턴하지 않는다.
  • 즉, 스트림을 리턴하지 않는 건 모두 종료 오퍼레이션이다.
  • collect, allMatch, count, forEach, min, max, ...

 

 

 


<2> Stream API



걸러내기

  • Filter(Predicate)
    예) 이름이 3글자 이상인 데이터만 새로운 스트림으로 

메소드 레퍼런스 사용할 때는 논리연산자 not인 느낌표(!) 붙이는 게 불가능하다.

대신 Predicate 인터페이스에 스태틱 메소드 not()을 붙여 사용할 수 있다.

Predicate.not()

 

 

변경하기

  • Map(Function) 
    예) 각각의 Post 인스턴스에서 String title만 새로운 스트림으로

 

  •  FlatMap(Function) : 스트림 평면화. 중복 구조로 되어있는 리스트를 하나의 스트림처럼 다룰 수 있다.
    예) List<Stream<String>>을 String의 스트림으로
animal = ["cow", "pig"]

List<String[]> results = animals.stream().map(animal -> animal.split(""))
                                .collect(Collectors.toList());

반환값: [["c","o","w"],["p","i","g"]]

 

List<String> results = animals.stream().map(animal -> animal.split(""))
        .flatMap(Arrays::stream)
        .collect(Collectors.toList());

반환값: ["c","o","w","p","i","g"]

 

 

생성하기

  • generate(Supplier) 또는 Iterate(T seed, UnaryOperator)
    예) 10부터 1씩 증가하는 무제한 숫자 스트림
    예) 랜덤 int 무제한 스트림
// 10부터 1씩 증가하는 무제한 스트림 중, 앞 10개 빼고 최대 10개까지만
Stream.iterate(10, i -> i + 1)
	.skip(10)
	.limit(10)
	.forEach(System.out::println);
	// 20~29 출력

 

 

제한하기

  • limit(long) 또는 skip(long)
    예) 최대 5개의 요소가 담긴 스트림을 리턴한다.
    예) 앞에서 3개를 뺀 나머지 스트림을 리턴한다.

 



스트림에 있는 데이터가 특정 조건을 만족하는지 확인

  • anyMatch()allMatch()nonMatch()
    예) k로 시작하는 문자열이 있는지 확인한다. (true 또는 false를 리턴한다.)
    예) 스트림에 있는 모든 값이 10보다 작은지 확인한다.

출력:

자바 수업 중에 어쩌구

true

스프링 수업 중에 어쩌구

spring boot

spring data jpa

spring mvc

spring core

 

순서가 바뀌면 달라진다. map이 먼저 오면 그 다음 filter는 문자열로 옴..

어떤 오퍼레이터는 순서에 따라 그 다음 오퍼레이터에 지나가는 타입이 바뀔 수 있으므로 순서에 주의해야 한다.

 

 

개수 세기

  • count()
    예) 10보다 큰 수의 개수를 센다.

 

 

스트림을 데이터 하나로 뭉치기

  • reduce(identity, BiFunction)collect()sum()max()
    예) 모든 숫자 합 구하기
    예) 모든 데이터를 하나의 List 또는 Set에 옮겨 담기

 

 

 



* 백기선 님의 인프런 강의 <더 자바, Java 8>을 듣고 정리한 내용입니다.

강의 정보: https://www.inflearn.com/course/the-java-java8/


참고

https://devjem.tistory.com/41

https://dev-kani.tistory.com/28

https://n1tjrgns.tistory.com/292
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html