본문 바로가기
JAVA

Java Stream 결과 만들기

by 봄석 2019. 9. 1.

Java Stream 결과 만들기

 

https://beomseok95.tistory.com/216

 

Java Stream알아보기

Java Stream JavaStream이란 ? Java8 부터 추가된 기능으로 ,"컬렉션, 배열등의 저장 요소를 하나씩 참조하며 함수형 인터페이스(람다식)를 적용하며 반복적으로 처리할 수 있도록 해주는 기능"입니다. (InputStre..

beomseok95.tistory.com

https://beomseok95.tistory.com/217

 

Java Stream 생성하기

Java Stream 생성하기 1. 생성하기 배열 , 컬렉션, 빈스트림 Stream.builder() , Stream.generate() , Stream.iterate() 기본타입형 , String , 파일스트림 병렬스트림, 스트림연결하기 배열스트림 , 컬렉션스트림..

beomseok95.tistory.com

https://beomseok95.tistory.com/218?category=1064782

 

Java Stream 가공하기

Java Stream 가공하기 https://beomseok95.tistory.com/216 Java Stream알아보기 Java Stream JavaStream이란 ? Java8 부터 추가된 기능으로 ,"컬렉션, 배열등의 저장 요소를 하나씩 참조하며 함수형 인터페이스(..

beomseok95.tistory.com

 

 

결과 만들기

스트림을 생성, 가공한후 마지막의 결과도출을 해내는 단계입니다.

스트림을 끝내는 최종 작업이라고 할 수 있습니다.

 

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

댓글