이전 Stream 사용법 포스트에서 내용을 보충하고자 작성한 글입니다.
이전 포스트 끝자락에서도 설명했지만 Filter나 Map 같은 데이터 수정(가공) 즉, 중간 작업만으로는 의미있는 Stream 객체를 만들 수 없기 때문에, 이 가공한 값을 출력하거나 컬렉션으로 모으는 등 마무리 작업이 필요합니다. 이 마무리 작업에 대해 설명해보겠습니다.
1. 결과 생성
1.1 집계(Aggregate)
int sum = IntStream.range(1, 10).sum();
int count = IntStream.range(1, 10).count();
int max = IntStream.range(1, 10).max();
int min = IntStream.range(1, 10).min();
int avg = IntStream.range(1, 10).average();
// 응용 : 짝수의 합
int evenSum = IntStream.range(1, 10)
.filter(v -> ((v % 2) == 0))
.sum();
sum(합), count(카운팅), max(최댓값), min(최솟값), average(평균)는 최종 결과를 산출하는 집계 메서드입니다.
이 때, 이 집계 메서드는 return 값으로 Optional 클래스를 return합니다.
public static void main(String[] args){
int[] intArr = {1, 2, 3, 4, 5};
// average() 요소 평균 반환
double avg = Arrays.stream(intArr)
.filter(n -> n%2 == 0)
.average()
.getAsDouble();
System.out.println(avg);
// max() 최대값 반환
int max = Arrays.stream(intArr)
.filter(n -> n%2 == 0)
.max()
.getAsInt();
System.out.println(max);
}
위와 같이 Optional의 값을 얻기 위해, get(), getAsDouble, getAs() 메서드로 호출이 가능합니다.
Optional 클래스는 단순 집계값 저장 뿐 아니라, 집계값이 존재하지 않을 경우 default 값을 설정할 수 있고(map의 getordefault와 유사), 집계값을 처리하는 Consumer도 등록할 수 있습니다. 코드로 살펴보면
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
// isPresent()
OptionalDouble optional = list.stream()
.mapToInt(Integer::intValue)
.average();
if(optional.isPresent()){
System.out.println("평균 : " + optional.getAsDouble());
}else{
System.out.println("nothing");
}
// orElse()
double avg = list.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
System.out.println("평균 : " + avg);
// ifPresent()
list.stream()
.mapToInt(Integer::intValue)
.average()
.ifPresent(data -> System.out.println("평균 : " + data));
}
list에 아무런 element가 없다면 예외(NoSuchElement)가 발생하는데, 이를 회피하기 위해
isPresent()는 값이 저장 여부를 판별,
orElse(...)는 값이 저장되어 있지 않을 경우에 default 값을 지정할 수 있으며,
ifPresent(Consumer ...)는 값이 저장되어 있다면, Consumer(예제 같은 경우는 data -> Sout)
메서드를 통해 처리할 수 있습니다.
1.2 Reduce
Reduce는 인자로 BinaryOperator 객체를 받는데, BinaryOperator는 T 타입의 인자 두개를 받고 T 타입의 객체를 리턴하는 함수형 인터페이스입니다. 여기서 연산을 수행하는 부분은 accumulator 함수이며, 직접 구현해서 인자로 전달해야 합니다.
Stream에서 1부터 10까지 숫자가 전달될 때, 이 값을 모두 합하는 코드를 작성한다고 해보겠습니다.
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s)); // sum: 55
BinaryOperator는 (total, n) -> total + n와 같은 형식으로 인자가 전달되는데 Stream의 1이 전달될 때, total(0) + n(1) = 1와 같이 계산되고, 여기서 리턴되는 1이 다음에 Stream에서 2가 전달될 때 total로 전달됩니다.
- total(0) + n(1) = 1
- total(1) + n(2) = 3
- ...
- total(36) + n(9) = 45
- total(45) + n(10) = 55
최종적으로 1부터 10까지의 합 55를 출력하게 됩니다.
1.3 Collect
Collect는 Stream을 List나 Set 등 Collection으로 변경시키는 Stream 메서드입니다. 바로 코드로 살펴보겠습니다.
List<String> alphabet = Arrays.asList("a", "bb", "cc", "bb");
List<String> result = alphabet.stream().collect(Collectors.toList()); // a, bb, ccc, dd
Set<String> result = alphabet.stream().collect(Collectors.toSet()); // a, bb, ccc
먼저 toList, toSet입니다. 모든 Stream의 element를 List 혹은 Set 인스턴스로 변경합니다. 이 때 특정한 Collection으로 변환되는 것이 아니라 더 자세한 Collection을 원한다면
alphabet.stream().collect(Collectors.toCollection(LinkedList::new));
toCollection메서드를 이용해 특정 Collection(위 같은 경우 LinkedList)으로 implementation 가능합니다.
String result = alphabet.stream().collect(joining()); // abbcccdd
joining()은 List가 아닌 String으로 문자열을 붙여줄 때 사용합니다.
1.4 forEach
forEach()는 해당 Stream 요소를 하나씩 소모하여 순차적으로 요소에 접근하는 메서드입니다. 이 때 같은 Stream으로는 forEach() 메서드를 한 번밖에 호출할 수 없습니다. (원본 데이터를 소모하는 것은 아니기 때문에, 같은 데이터에서 또 다른 Stream 생성 후 사용은 가능)
Set<Integer> evenNumber = IntStream.range(1, 100).boxed()
.filter(n -> (n%2 == 0))
.forEach(System.out::println);
1부터 999까지를 filter를 통해 짝수만을 가져오도록 filtering한 뒤, 순차적으로 접근하여 짝수만을 출력하게 됩니다.
2. 마무리
사실 Stream의 모든 메서드를 설명한 것이 아니라 그저 Stream과 람다식을 공부하고자 작성한 글입니다. Stream에 좀 익숙해지면 API 문서에 자주 사용하는 Stream 메서드를 한 번 정리해보겠습니다.
참조
'Skills > Java' 카테고리의 다른 글
Java - 불필요한 객체 생성을 피하라 (0) | 2022.12.10 |
---|---|
Java - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2022.12.05 |
Java - Stream 사용법 및 예제 (0) | 2022.11.25 |
Java - FIrst Class Collection(일급 컬렉션) (0) | 2022.11.17 |
Java - NumberFormatException 에러 (0) | 2022.11.14 |