Java Stream 결과 만들기
https://beomseok95.tistory.com/216
https://beomseok95.tistory.com/217
https://beomseok95.tistory.com/218?category=1064782
결과 만들기
스트림을 생성, 가공한후 마지막의 결과도출을 해내는 단계입니다.
스트림을 끝내는 최종 작업이라고 할 수 있습니다.
Calculating
스트림 API는 다양한 종료 작업을 제공합니다.
최소 , 최대, 합, 평균 등 기본형 타입으로 결과를 만들어 낼 수 있습니다.
int[] ints = {1, 3, 5, 7, 9};
long isCount = IntStream.of(ints).count();
long isSum = IntStream.of(ints).sum();
println("count " + isCount);
println("sum " + isSum);
//result
count 5
sum 25
만약 스트림이 비어있을 경우 count와 sum 은 0 이 출력됩니다.
하지만 평균, 최소 , 최대의 경우는 리스트가 비어있다면 표현할 수가 없습니다.
그렇기때문에 Optional을 이용해 리턴합니다.
int[] ints = {1, 3, 5, 7, 9};
IntStream.of(ints).min().ifPresent(System.out::println);
IntStream.of(ints).max().ifPresent(System.out::println);
int isMin = IntStream.of(ints).min().getAsInt();
println(String.valueOf(isMin));
int isMax = IntStream.of(ints).max().getAsInt();
println(String.valueOf(isMax));
//result
1
9
1
9
스트림에서 isPresent를 이용하여 바로 Optional을 처리할 수 있습니다.
평균도 마찬가지로 average()를 이용하여 처리할 수 있습니다.
int[] ints = {1, 3, 5, 7, 9};
IntStream.of(ints)
.average()
.ifPresent(System.out::println);
Reduce
스트림은 reduce라는 메소드를 이용하여 결과를 만들어 낼 수 있습니다.
reduce는 여러요소의 총 합을 나타낼때 주로 사용합니다.
reduce메소드는 총 3가지의 메소드를 받을 수 있습니다.
- 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);
1. 인자를 1개만 받을 경우
OptionalInt reduced =
IntStream.range(1, 4) // [1, 2, 3]
.reduce((a, b) -> {
return Integer.sum(a, b);
});
//result
6(1+2+3)
인자를 1개받는데 , BinaryOperator를 받는것을 알 수 있습니다.
BinaryOperator 는 같은 타입의 인자를 두 개 받아 같은 타입의 결과를 반환하는 함수형 인터페이스 입니다.
위 예를 보면 두값을 더하는 람다를 넘기고 있습니다.
2. 인자를 2개 받을 경우
int reduced = IntStream.rangeClosed(1, 5)
.reduce(0, (a, b) -> {
return Integer.sum(a, b);
});
println("reduced " + reduced);
//result
reduced 15
인자를 2개 받을 경우, 위의 예에서는 identity(초깃값)으로 10을 받고 , BinaryOperator는 마찬가지로 두값을 더하는 람다를 전달받고있습니다.
3. 인자를 3개 받을 경우
Integer reducedParams = Stream.of(1, 2, 3)
.reduce(10, // identity
Integer::sum, // accumulator
(a, b) -> {
System.out.println("combiner was called");
return a + b;
});
인자를 3개 받을 경우는 ifdentify ,accumulator, Combiner를 받습니다.
하지만 위를 실행해보면 이상하게 Combiner 는 호출되지 않습니다.
그 이유는 인자를 3개 받을 때 combiner의 역할은 병렬 처리 시 각자 다른 스레드에서 실행한 결과를 합치는 역할을 합니다.
따라서 병렬 스트림에서만 동작합니다.
Integer reducedParallel = Arrays.asList(1, 2, 3)
.parallelStream()
.reduce(10,
Integer::sum,
(a, b) -> {
System.out.println("combiner was called");
return a + b;
});
println("reducedParallel "+reducedParallel);
//result
combiner was called
combiner was called
reducedParallel 36
병렬스트림으로 만들어 작동한다면
초기값 10에 각각의 값을더하게됩니다(10+1) + (10+2) +(10+3) = 36
즉, combiner의 역할을 identity와 accumulator를 가지고 여러스레드에서 나눠 계산할 결과를 합치는 역할입니다.
Collecting
collect메소드는 stream종료 작업중 하나로
타입의 안자를 받아서 처리하는데 , 그 타입 인자는 Coolectors에서 제공하고 있습니다.
아래 예제들은 Product라는 클래스를 이용하여 처리하도록 하겠습니다.
public class Product {
private int amount = 0;
private String name = "";
public Product(int amount, String name) {
this.amount = amount;
this.name = name;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Collectors.toList()
스트림에서 작업한 결과를 담은 리스트를 반환합니다.
productList.stream()
.map(Product::getName)
.collect(Collectors.toList());
//result
potatoes,orange,lemon,bread,sugar
Collectors.joining()
스트림에서 작업한 결과를 하나의 스트링으로 이어 붙일 수 있습니다.
String listToString =
productList.stream()
.map(Product::getName)
.collect(Collectors.joining(", ", "<", ">"));
//result
<potatoes, orange, lemon, bread, sugar>
Collectors.joining 은 세 개의 인자를 받을 수 있습니다. 이를 이용하면 간단하게 스트링을 조합할 수 있습니다.
delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
prefix : 결과 맨 앞에 붙는 문자
suffix : 결과 맨 뒤에 붙는 문자
Collectors.averageingInt()
숫자 값(Integer value )의 평균(arithmetic mean)을 냅니다.
productList.stream()
.collect(Collectors.averagingInt(Product::getAmount));
//result
17.2
Collectors.summingInt()
숫자값의 합(sum)을 냅니다.
productList.stream().collect(Collectors.summingInt(Product::getAmount));
productList.stream().mapToInt(Product::getAmount).sum();
//result
86
mapToInt를 사용해서도 합을 구할 수 있습니다.
Collectors.summarizingInt()
만약 합계와 평균 모두 필요하다면 스트림을 두 번 생성해야 할까요?
이런 정보를 한번에 얻을 수 있는 방법으로는 summarizingInt 메소드가 있습니다.
productList.stream()
.collect(Collectors.summarizingInt(Product::getAmount));
//result
IntSummaryStatistics{count=5, sum=86, min=13, average=17.200000, max=23}
- 개수 getCount()
- 합계 getSum()
- 평균 getAverage()
- 최소 getMin()
- 최대 getMax()
등이 담겨진 객체를 리턴받게됩니다.
Collectors.groupingBy()
원형
public static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M> groupingBy(
Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)
함수 파라미터는 다음과 같습니다
-
classifier : groupBy를 위한 기준값을 가져오는 function
-
mapFactory : 함수의 수행 결과로서 새롭게 만들어지는 Map을 생성하는 function
-
downstream : groupBy의 수행결과로서 얻어지는 결과 Collector
특정 조건으로 요소들을 그룹지을 수 있습니다.
Product 객체의 amount를 기준으로 그룹핑한 예입니다.
productList.stream()
.collect(Collectors.groupingBy(Product::getAmount));
//result
{23=[stream.result.Product@4501b7af, stream.result.Product@523884b2], 13=[stream.result.Product@5b275dab, stream.result.Product@61832929], 14=[stream.result.Product@29774679]}
수량 (amount)에 따라서 Product객체가 리스트로 묶이게 됩니다.
Map<Integer, List<Product>>형태로 묶이게 되었습니다.
Collectors.partitioningBy()
위의 groupingBy 함수형 인터페이스 Function 을 이용해서 특정 값을 기준으로 스트림 내 요소들을 묶었다면, partitioningBy 은 함수형 인터페이스 Predicate 를 받습니다. Predicate 는 인자를 받아서 boolean 값을 리턴합니다.
productList.stream()
.collect(Collectors.partitioningBy(e -> e.getAmount() > 14));
//result
{false=[stream.result.Product@29774679, stream.result.Product@5b275dab, stream.result.Product@61832929], true=[stream.result.Product@4501b7af, stream.result.Product@523884b2]}
조건문의 true ,false를 기준으로 나뉘어지게 됩니다.
Collectors.collectingAndThen()
특정 타입으로 결과를 collect 한 이후에 추가 작업이 필요한 경우에 사용할 수 있습니다. 이 메소드의 시그니쳐는 다음과 같습니다. finisher 가 추가된 모양인데, 이 피니셔는 collect 를 한 후에 실행할 작업을 의미합니다.
collectingAndThen function의 원형
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(
Collector<T,A,R> downstream,
Function<R,RR> finisher) { ... }
아래의 예제는 Collectors.toSet을 이용하여 결과를 Set으로 collect한 후
수정불가능한 set으로 변환하는 작업입니다.
Set<Product> unmodifiableSet =
productList.stream()
.collect(Collectors.collectingAndThen(Collectors.toSet(),
Collections::unmodifiableSet));
//result -수정 불가능한 Set<Product>
[stream.result.Product@523884b2, stream.result.Product@61832929, stream.result.Product@5b275dab, stream.result.Product@4501b7af, stream.result.Product@29774679]
Collectors.of()
여러가지 상황에서 사용할 수 있는 메소드들을 살펴봤습니다. 이 외에 필요한 로직이 있다면 직접 collector 를 만들 수도 있습니다. accumulator 와 combiner 는 reduce 에서 살펴본 내용과 동일합니다.
//of function의 원형
public static<T, R> Collector<T, R, R> of(
Supplier<R> supplier, // new collector 생성
BiConsumer<R, T> accumulator, // 두 값을 가지고 계산
BinaryOperator<R> combiner, // 계산한 결과를 수집하는 함수.
Characteristics... characteristics) { ... }
Collector<Product, ?, LinkedList<Product>> toLinkedList =
Collector.of(LinkedList::new,
LinkedList::add,
(first, second) -> {
first.addAll(second);
return first;
});
LinkedList<Product> linkedListOfPersons =
productList.stream()
.collect(toLinkedList);
//result
[stream.result.Product@4501b7af, stream.result.Product@29774679, stream.result.Product@5b275dab, stream.result.Product@523884b2, stream.result.Product@61832929]
다음 코드에서는 collector 를 하나 생성합니다. 컬렉터를 생성하는 supplier 에 LinkedList 의 생성자를 넘겨줍니다. 그리고 accumulator 에는 리스트에 추가하는 add메소드를 넘겨주고 있습니다. 따라서 이 컬렉터는 스트림의 각 요소에 대해서 LinkedList 를 만들고 요소를 추가하게 됩니다. 마지막으로 combiner 를 이용해 결과를 조합하는데, 생성된 리스트들을 하나의 리스트로 합치고 있습니다.
Matching
매칭은 조건식 람다 Predicate 를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴합니다. 다음과 같은 세 가지 메소드가 있습니다.
- 하나라도 조건을 만족하는 요소가 있는지(anyMatch)
- 모두 조건을 만족하는지(allMatch)
- 모두 조건을 만족하지 않는지(noneMatch)
List<String> names = Arrays.asList("Eric", "Elena", "Java");
boolean anyMatch = names.stream()
.anyMatch(name -> name.contains("a"));
boolean allMatch = names.stream()
.allMatch(name -> name.length() > 3);
boolean noneMatch = names.stream()
.noneMatch(name -> name.endsWith("s"));
println("anyMatch " + anyMatch);
println("allMatch " + allMatch);
println("noneMatch " + noneMatch);
//result
anyMatch true
allMatch true
noneMatch true
Iterating
foreach 는 요소를 돌면서 실행되는 최종 작업입니다.
보통 System.out.println 메소드를 넘겨서 결과를 출력할 때 사용하곤 합니다.
앞서 살펴본 peek 과는 중간 작업과 최종 작업의 차이가 있습니다.
productList.stream()
.map(Product::getName)
.forEach(name -> System.out.println("collectionIterating " + name));
//result
collectionIterating potatoes
collectionIterating orange
collectionIterating lemon
collectionIterating bread
collectionIterating sugar
'JAVA' 카테고리의 다른 글
Java - 메모리관리 ( 스택& 힙) [펌] (0) | 2019.09.01 |
---|---|
Primitive vs Reference (0) | 2019.09.01 |
Java Stream 가공하기 (0) | 2019.09.01 |
Java Stream 생성하기 (0) | 2019.09.01 |
Java Stream알아보기 (0) | 2019.09.01 |
댓글