프로그래밍 언어/Java

Stream이란? - 최종 처리 메소드의 종류와 사용 방법 [1] (JAVA)

제이온 (Jayon) 2021. 1. 7.

안녕하세요? 코딩중독입니다.

 

저번 시간에는 루핑을 제외한 중간 처리 메소드에 대해 알아 보았습니다. 오늘은 최종 처리 메소드의 종류와 사용 방법을 설명하겠습니다. 하나의 포스팅으로는 끝나지 않을 듯해서 부득이하게 3개의 포스팅으로 나눌 것 같습니다.

 

 

최종 처리 메소드의 종류

이것이 자바다 16강 인강 중 사용된 학습 자료

 

 

중간 처리 메소드의 리턴 타입은 스트림이었던 반면에, 최종 처리 메소드는 기본 타입이거나 OptionalXXX입니다. 또한, 소속된 인터페이스가 공통이라는 의미는 Stream, IntStream, LongStream, DoubleStream에서 모두 제공된다는 뜻입니다.

 

종류로는 루핑, 매칭, 집계, 수집이 있는데 이번 시간에는 집계까지만 살펴보도록 하겠습니다.

 

 

루핑

루핑은 특이하게도 중간 처리 메소드에서도 사용되고, 최종 처리 메소드에서도 사용됩니다. 그렇다면, 이 둘의 차이는 무엇일까요?

 

기능 자체는 루핑한다는 것에서 동일하지만, peek() 최종 처리 메소드가 없으면 동작하지않습니다. forEach() 메소드는 그 자체로 최종 처리 메소드이기때문에 루핑이 정상적으로 작동합니다.

 

이것은 소스코드를 통해 자세히 보겠습니다.

 

 

 

 

출력 결과는 다음과 같습니다.

 

 

 

 

최종 처리 메소드없이 peek()만 호출하면 그 안에 있는 짝수 요소가 출력되지 않으며, 최종 처리 메소드가 존재할 때 peek()을 호출하면 짝수 요소가 출력된다는 사실을 알 수 있습니다. 또한, forEach()는 그 자체로 최종 처리 메소드이므로 마지막에 사용함으로써 짝수 요소를 출력하게 만들 수 있습니다.

 

 

매칭

매칭은 최종 처리 단계에서 요소들이 특정 조건에 만족하는지 조사할 수 있도록 설계한 것입니다. 비슷한 역할로는 중간 처리 단계에서 필터링이 있겠네요.

 

리턴 타입은 모두 boolean이고, 메소드는 allMatch(), anyMatch(), noneMatch()가 있습니다. 그리고 이 메소드들의 매개 변수는 Predicate를 사용합니다. 제공 인터페이스가 Stream일 경우에는 Predicate<T>지만, 나머지에 대해서는 IntPredicate 또는 LongPredicate 또는 DoublePredicate입니다.

 

크게 어려운 내용은 아니므로 바로 예제 코드를 보겠습니다.

 

 

 

 

모든 요소가 2의 배수인지, 하나라도 3의 배수가 존재하는지, 모든 요소가 3의 배수가 아닌지를 조사하는 예제입니다. 출력 결과는 true, true, false가 나올 것입니다.

 

 

기본 집계

집계는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최댓값, 최솟값 등과 같이 하나의 값으로 산출하는 것을 말합니다.

 

 

 

 

위와 같이 count()와 sum()을 제외하면 리턴 타입이 Optional이라는 것을 알 수 있습니다. OptionalXXX는 자바 8에서 추가한 java.util 패키지의 새로운 클래스 타입으로, 값을 저장하는 값 기반 클래스들입니다. 이 객체에서 값을 얻기 위해서는 get(), getAsDouble(), getAsInt(), getAsLong()을 호출하면 됩니다.

 

Optional 클래스를 자세히 알아보기 전에 기본 집계 메소드들을 사용한 예제 코드부터 살펴봅시다.

 

 

 

 

실행 결과는 아래와 같습니다.

 

 

 

 

Optional 클래스

기본 집계에서 이어지는 내용입니다. Optional 클래스는 위에서 값을 저장하는 값 기반 클래스라고 하였는데, 사실 값만 저장하는 것이 아니라, 집계 값이 존재하지 않을 경우 디폴트 값을 설정할 수 있고, 집계 값을 처리하는 Consumer도 등록할 수 있습니다.

 

아래는 Optional 클래스에서 제공하는 메소드 목록입니다.

 

 

 

 

값이 저장되어 있는지 여부가 필요한 경우는 많습니다. 컬렉션의 요소는 동적으로 추가되는 경우가 많은데, 만약 컬렉션의 요소가 추가되지 않아 저장된 요소가 없을 경우, 평균값을 구하거나 합을 구하는 작업을 수행할 수 없습니다. 요소가 하나도 없기 때문이죠.

 

이러한 예외(NoSuchElementException)를 막기 위한 방법은 총 3가지가 있습니다. 이해를 돕기 위하여 아래 코드와 같은 상황을 하나 설정하겠습니다.

 

 

 

 

(1) Optional 객체를 얻어서 isPresent() 메소드 사용

isPresent() 메소드는 값이 저장되어 있는지 확인하는 메소드로, 가장 기본적으로 if문을 활용하여 작성하면 됩니다.

 

 

 

 

(2) orElse() 메소드로 디폴트 값 설정

orElse() 메소드는 값이 저장되어 있지 않을 경우 디폴트 값을 지정하는 역할을 합니다. 참고로 orElse() 메소드를 사용할 경우, 디폴트 값을 지정하는 동시에 값을 반환합니다. getXXX() 메소드를 쓸 필요가 없는 것이죠.

 

 

 

 

(3) ifPresent() 메소드 사용

ifPresent() 메소드는 값이 저장되어 있을 경우 Consumer에서 연산을 수행하도록 합니다. 이때, Consumer 안에서 람다식으로 하고자 하는 작업을 표현할 수 있습니다.

 

 

 

 

위 예제 코드에서는 list에 저장된 값이 없으므로 아무것도 출력이 되지 않습니다.

 

 

커스텀 집계

스트림은 기본 집계말고도 reduce() 메소드라는 커스텀 집계를 제공합니다. 직접 기준을 세워서 집계를 하는 것이죠.

 

 

 

 

사용하는 인터페이스는 Stream, IntStreaem, LongStream, DoubleStream이 있고, 리턴 타입은 Optional 혹은 T로 되어 있습니다. Optional과 T로 리턴되는 기준은 바로 메소드의 매개변수 중 identity의 유무입니다.

 

identity 매개값은 쉽게 말해서 디폴트값이라고 보면 됩니다. 자세한 reduce() 메소드의 동작 과정은 소스코드를 통해 살펴 봅시다.

 

 

 

 

전자는 identity 매개값을 설정하지 않은 것이고, 후자는 설정한 것입니다. 그리고 전자는 위의 Optional 클래스에서 설명한대로 디폴트값에 대한 예외 처리를 해 주어야 합니다. 반면, 후자는 이미 reduce() 메소드에서 디폴트값을 설정해 주었기때문에 리스트이 요소가 없어도 예외가 발생하지 않습니다.

 

reduce는 기본적으로 스트림의 요소를 하나씩 방문하면서 개발자가 정한 기준에 따라서 누적 연산을 합니다. 위 예제 코드에서는 덧셈 연산으로 설정하였기때문에 멤버의 나이를 누적 덧셈을 하게 됩니다.

 

출력 결과는 둘다 66이 나오게 됩니다.

 

 

정리

지금까지 수집을 제외한 최종 처리 메소드에 대해서 알아 보았습니다. 다음 시간부터는 수집을 활용한 collect() 메소드를 다루겠습니다.

 

 

참고 자료

 

JAVA chapter16. 스트림과 병렬처리. 16.10 커스텀 집계(reduce())

JAVA chapter16. 스트림과 병렬처리. 16.10 커스텀 집계(reduce()) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package sec10.stream_reduce; import java.util..

kkk-kkk.tistory.com

 

 

 

 

이것이 자바다. - 저자 신용권

댓글

추천 글