2023. 12. 4. 20:46ㆍ카테고리 없음
Stream의 구조
Stream 최종연산
중간 연산한 스트림을 가지고 결과를 반환하는 단계. 스트림을 끝내는 최종 작업(terminal operations)
연산 | 반환형식 | 설명 |
forEach | void | 스트림의 각 요소를 소비 |
count | long | 스트림의 요소 개수 반환 |
collect | Collection | List, Map 형태의 컬렉션을 반환 |
sum | long | 스트림의 모든 요소에 대한 합계를 반환 |
reduce | Optional | 스트림의 요소를 하나씩 줄여가며 연산 수행 후 결과를 반환 |
3. 최종연산
계산하기(Calculating)
스트림 API는 최소(min), 최대(max), 합(sum), 평균(avg) 등의 기본형 타입의 결과를 반환할 수 있는 다양한 종료 작업을 제공
count, sum은 스트림이 비어 있는 경우 0을 반환.
//예시코드
long count1 = IntStream.of().count();
long count2 = IntStream.of(11,22,33,44,55).count();
long sum1 = LongStream.of().sum();
long sum2 = LongStream.of(22L,33L,44L).sum();
System.out.print("count1::");
LongStream.of(count1).forEach(System.out::print);
System.out.println();
System.out.print("count2::");
LongStream.of(count2).forEach(System.out::print);
System.out.println();
System.out.print("sum1::");
LongStream.of(sum1).forEach(System.out::print);
System.out.println();
System.out.print("sum2::");
LongStream.of(sum2).forEach(System.out::print);
//출력
count1::0
count2::5
sum1::0
sum2::99
min, max 는 스트림이 비어있는 경우 Optional 객체를 반환.
//예시코드
int min1 = IntStream.of().min().orElseGet(()->00);
OptionalInt min2 = IntStream.of(11,22,33,44,55).min();
int max1 = IntStream.of().min().orElseGet(()->99);
OptionalInt max2 = IntStream.of(11,22,33,44,55).max();
System.out.print("min1::");
IntStream.of(min1).forEach(System.out::print);
System.out.println();
System.out.print("min2::");
min2.stream().forEach(System.out::print);
System.out.println();
System.out.print("max1::");
IntStream.of(max1).forEach(System.out::print);
System.out.println();
System.out.print("max2::");
max2.stream().forEach(System.out::print);
//출력
min1::0
min2::11
max1::99
max2::55
결과합치기(Reduction)
reduce는 아래와 같이 총 세 가지의 파라미터를 받을 수 있음
- accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
- identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
- combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);
// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);
// 3개 (combiner)
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
아래는 reduce 내 인자를 1개 사용할 경우의 예시.
BinaryOperator<T> 같은 타입의 인자 2개를 받아 같은 타입의 인자를 반환하는 함수형 인터페이스.
아래에서는 (2+3+4+5 = 16) 결과를 반환.
//예시코드(람다식)
OptionalInt acc1 = IntStream.range(2,6) // [2,3,4]
.reduce((a, b) -> {
System.out.println("a::"+a);
System.out.println("b::"+b);
return Integer.sum(a, b);
});
acc1.stream().forEach(System.out::println);
//예시코드(메서드참조사용)
System.out.println("메서드참조사용~");
OptionalInt acc2 = IntStream.range(2, 6).reduce(Integer::sum);
acc2.stream().forEach(System.out::println);
//출력
a::2
b::3
a::5
b::4
a::9
b::5
14
메서드참조사용~
14
아래는 reduce 내 인자를 2개 사용한 예시.
첫번째 인자는 초기값(identity)으로 비어있는 스트림결과를 반환할 지라도 초기값을 반환함.
아래는 (20(초기값)+2+3+4+5+6 = 34) 결과를 반환.
//예시코드
System.out.println("메서드참조사용 & 두 개인자");
int identyAcc = IntStream.range(2, 6).reduce(20, Integer::sum);
IntStream.of(identyAcc).forEach(System.out::println);
//출력
메서드참조사용 & 두 개인자
34
아래는 reduce 내 인자를 3개 사용한 예시.
[예시코드(병렬처리안함)]
- '병렬처리안함' 예시의 경우 인자를 3개를 받았지만, 마지막 인자의 값은 출력되지 않은 것을 확인
- 결과는 (10(초기값)+2+4+6 = 22) 출력
[예시코드(병렬처리)]
- Combiner(마지막인자)가 identity 와 accumulator를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할을 함.
- '병렬처리' 예시의 경우 인자를 3개를 받아서 병렬처리 후 마지막 인자의 값까지 출력되는 것을 확인
- 결과는 ( 10(초기값)+2 + 10(초기값)+4 + 10(초기값)+6 = 42) 출력
//예시코드(병렬처리안함)
System.out.println("메서드참조사용 & 세 개인자 & 병렬처리안할경우(마지막인자출력안됨)");
Integer reducedParams = Stream.of(2, 4, 6)
.reduce(10, // identity
Integer::sum, // accumulator
(a, b) -> { //combiner
System.out.println("combiner was called");
return a + b;
});
IntStream.of(reducedParams).forEach(System.out::println);
//예시코드(병렬처리)
System.out.println("메서드참조사용 & 세 개인자 & 병렬처리할경우(마지막인자출력)");
Integer reducedParallel = Arrays.asList(2, 4, 6)
.parallelStream()
.reduce(10,
Integer::sum,
(a, b) -> {
System.out.println("combiner was called");
return a + b;
});
IntStream.of(reducedParallel).forEach(System.out::println);
//출력
메서드참조사용 & 세 개인자 & 병렬처리안할경우(마지막인자출력안됨)
22
메서드참조사용 & 세 개인자 & 병렬처리할경우(마지막인자출력)
combiner was called
combiner was called
42
결과모으기(Collecting)
collect는 Collector 타입의 인자를 받아서 처리를 하며, 자주 사용하는 작업에 대해서는 Collectors 객체에서 제공
Collecting 예제에서는 객체 리턴값을 확인 하기 위하여 아래와 같이 간단한 Number 객체 타입을 사용.
//collect예시에서 사용할 객체
class Number{
int key = 0;
String value = "";
public Number(int k, String v){
this.key = k;
this.value = v;
}
public int getKey(){
return this.key;
}
public String getValue(){
return this.value;
}
public String toString() {
return "Number{key="+ this.key + ", value="+ this.value+"}";
}
}
Collectors.toList() : 스트림에서 작업한 결과를 List타입으로 반환 (아래 예시 리턴타입확인)
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third"));
List<String> list1 = numList.stream()
.map(m->m.getValue())
.collect(Collectors.toList());
list1.stream().forEach(System.out::println);
//출력
First
Second
Third
Collectors.joining() : 스트림에서 작업한 결과를 이어붙어 String타입으로 반환 (아래 예시 리턴타입확인)
Collectors.joining(delimiter, prefix, suffix) 총 3개의 인자를 받아서 사용 가능
- delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
- prefix : 결과 맨 앞에 붙는 문자
- suffix : 결과 맨 뒤에 붙는 문자
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third"));
String join1 = numList.stream()
.map(m->m.getValue())
.collect(Collectors.joining());
Stream.of(join1).forEach(System.out::println);
String join2 = numList.stream()
.map(m->m.getValue())
//.collect(Collectors.joining(delimiter, prefix, suffix));
.collect(Collectors.joining(",","[","]"));
Stream.of(join2).forEach(System.out::println);
//출력
FirstSecondThird
[First,Second,Third]
Collectors.averagingInt() : 스트림 결과의 평균을 리턴
Collectors.summingInt() : 스트림 결과의 합계를 리턴
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third"));
Double average = numList.stream()
.collect(Collectors.averagingInt(Number::getKey));
Integer sum = numList.stream()
.collect(Collectors.summingInt(Number::getKey));
Integer sum2 = numList.stream()
.mapToInt(Number::getKey)
.sum();
System.out.print("avg::");
DoubleStream.of(average).forEach(System.out::print);
System.out.println();
System.out.print("sum::");
IntStream.of(sum).forEach(System.out::println);
System.out.println();
System.out.print("sum2(mapToInt사용)::");
IntStream.of(sum2).forEach(System.out::println);
//출력
avg::2.0
sum::6
sum2(mapToInt사용)::6
Collectors.summarizingInt() : 스트림 결과에 대해 count(갯수), sum(합계), min(최솟값), average(평균), max(최댓값)을 한번에 리턴
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third"));
IntSummaryStatistics calc =numList.stream()
.collect(Collectors.summarizingInt(Number::getKey));
System.out.println("calc::");
Stream.of(calc).forEach(System.out::print);
//출력
calc::
IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}
Collectors.groupingBy() : 특정 조건으로 요소들을 그룹 지을 수 있음
//예시코드 (key값으로 그룹핑 -> key는 유니크한 값이므로 그룹핑 할 게 없음)
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third"));
Map<Integer, List<Number>> groupingBy = numList.stream().collect(Collectors.groupingBy(Number::getKey));
Stream.of(groupingBy).forEach(System.out::println);
//출력
{1=[Number{key=1, value=First}], 2=[Number{key=2, value=Second}], 3=[Number{key=3, value=Third}]}
//예시코드 (value값으로 그룹핑 -> 예제에서는 중복 value(Third_1) 임의 생성하여 출력)
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third_1"),
new Number(4, "Third_1"));
Map<String, List<Number>> groupingBy = numList.stream().collect(Collectors.groupingBy(Number::getValue));
Stream.of(groupingBy).forEach(System.out::println);
//출력
{Second=[Number{key=2, value=Second}], Third_1=[Number{key=3, value=Third_1}, Number{key=4, value=Third_1}], First=[Number{key=1, value=First}]}
Collectors.partitioningBy() : 위의 groupingBy 함수형 인터페이스 Function을 이용해서 특정 값을 기준으로 스트림 내 요소들을 그룹 지었다면, partitioningBy은 함수형 인터페이스 Predicate를 사용 ( Predicate는 인자를 받아서 boolean값을 리턴)
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third_1"),
new Number(4, "Third_1"));
Map<Boolean, List<Number>> partitioningBy =
numList.stream()
.collect(Collectors.partitioningBy(p -> p.getKey() > 2));
Stream.of(partitioningBy).forEach(System.out::println);
//출력
{false=[Number{key=1, value=First}, Number{key=2, value=Second}], true=[Number{key=3, value=Third_1}, Number{key=4, value=Third_1}]}
Collectors.collectingAndThen() : 특정 타입으로 결과를 collect한 이후에 추가 작업이 필요한 경우에 사용가능함. 이 메서드의 시그니처는 다음과 같은데, finisher가 추가된 형태로 이 finisher는 collect를 한 후에 실행할 작업을 의미
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(
Collector<T,A,R> downstream,
Function<R,RR> finisher) { ... }
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third_1"),
new Number(4, "Third_1"));
Set<Number> collectingAndThen = numList.stream()
.collect(Collectors.collectingAndThen(Collectors.toSet(),
Collections::unmodifiableSet));
Stream.of(collectingAndThen).forEach(System.out::println);
//출력
[Number{key=3, value=Third_1}, Number{key=1, value=First}, Number{key=4, value=Third_1}, Number{key=2, value=Second}]
Collectors.of() : 이 외에 필요한 로직이 있다면 직접 collector를 만들 수 있음
public static<T, R> Collector<T, R, R> of(
Supplier<R> supplier, // new collector 생성
BiConsumer<R, T> accumulator, // 두 값을 가지고 계산
BinaryOperator<R> combiner, // 계산한 결과를 수집하는 함수.
Characteristics... characteristics) { ... }
매칭(Matching)
Matching은 람다 Predicate를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴함.
- anyMatch: 하나라도 조건을 만족하는 요소가 있는지
- allMatch: 모두 조건을 만족하는지
- noneMatch: 모두 조건을 만족하지 않는지
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
//예시코드
List<Number> numList = Arrays.asList(new Number(1, "First"),
new Number(2, "Second"),
new Number(3, "Third_1"),
new Number(4, "Third_1"));
boolean anyMatch = numList.stream().anyMatch(n -> n.getValue().contains("e"));
boolean allMatch = numList.stream().allMatch(n -> n.getValue().length() > 2);
boolean noneMatch = numList.stream().noneMatch(n -> n.getValue().endsWith("s"));
System.out.println("anyMatch::" + anyMatch);
System.out.println("allMatch::" + allMatch);
System.out.println("noneMatch::" + noneMatch);
//출력
anyMatch::true
allMatch::true
noneMatch::true
참고 : https://futurecreator.github.io/2018/08/26/java-8-streams/#google_vignette