ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 스트림과 병렬 처리 - 루핑, 매칭, 집계
    CSE/Java 2015. 9. 10. 15:18
    스트림과 병렬 처리는 여러 절로 구성되어 있습니다.








    7. 루핑(peek(), forEach())

     루핑(looping)은 요소 전체를 반복하는 것을 말합니다.


     루핑하는 메소드에는 peek(), forEach()가 있습니다. 이 두 메소드는 루핑한다는 기능에서는 동일하지만, 동작 방식은 다릅니다.


     peek() 는 중간 처리 메소드이고, forEach()는 최종 처리 메소드 입니다.


     peek() 메소드는 중간 처리 단계에서 전체 요소를 루핑하면서 추가적인 작업을 하기 위해 사용합니다. 최종 처리 메소드가 실행되지 않으면 지연되기 때문에 반드시 최종 처리 메소드가 호출되어야 동작합니다.


     예를 들어 필터링 후 어떤 요소만 남았는지 확인하기 위해 다음과 같이 peek()를 마지막에 호출할 경우, 스트림은 동작하지 않습니다.



    1
    2
    3
    4
    intStream
        .filter( a -> a % 2 == 0
        .peek(a -> System.out.println(a))
     
    cs



     요소 처리의 최종 단계가 합을 구하는 것이라면, peek() 메소드 호출 후 sum() 을 호출해야만 peek()가 동작합니다.


    1
    2
    3
    4
    intStream
        .filter( a -> a % 2 == 0
        .peek(a -> System.out.println(a))
        .sum()
    cs




     하지만 forEach() 는 최종 처리 메소드이기 때문에 파이프라인 마지막에 루핑하면서 요소를 하나씩 처리합니다. forEach()는 요소를 소비하는 최종 처리 메소드이므로 이후에 sum() 과 같은 다른 최종 메소드를 호출하면 안 됩니다.



     * LoopingExam.java



    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
    package stream;
     
    import java.util.Arrays;
     
    public class LoopingExam {
     
        public static void main(String[] args) {
            int[] intArr = {54321};
            
            System.out.println("[peek()를 마지막에 호출한 경우]");
            Arrays.stream(intArr)
                .filter(a -> a % 2 == 0)
                .peek(n -> System.out.println(n));    // 동작하지 않음
            
            System.out.println("[최종 처리 메소드를 마지막에 호출한 경우]");
            int total = Arrays.stream(intArr)
                    .filter(a -> a % 2 == 0
                    .peek(n -> System.out.println(n))    // 동작함
                    .sum();
            System.out.println("총합: " + total);
            
            System.out.println("[forEach를 마지막에 호출한 경우]");
            Arrays.stream(intArr)
                .filter(a -> a % 2 == 0)
                .forEach(n -> System.out.println(n)); // 최종 메소드로 동작함
        }
     
    }
     
    cs



     














    8. 매칭(allMatch(), anyMatch(), noneMatch())

     스트림 클래스는 최종 처리 단계에서 요소들이 특정 조건에 만족하는지 조사할 수 있도록 세 가지 매칭 메소드를 제공하고 있습니다.


     allMatch() 메소드는 모든 요소들이 파라미터로 주어진 Predicate의 조건을 만족하는지 조사하고, anyMatch() 메소드는 최소한 한 개의 요소가 파라미터로 주어진 Predicate의 조건을 만족하는지 조사합니다.  그리고 noneMatch()는 모든 요소들이 파라미터로 주어진 Predicate의 조건을 만족하지 않는지 조사합니다.



     리턴 타입

     메소드(파라미터) 

     제공 인터페이스 

     boolean 

     allMatch(Predicate<T> predicate)

     anyMatch(Predicate<T> predicate)

     noneMatch(Predicate<T> predicate)

     Stream

     boolean 

     allMatch(IntPredicate predicate)

     anyMatch(IntPredicate predicate)

     noneMatch(IntPredicate predicate) 

     IntStream 

     boolean 

     allMatch(LongPredicate predicate)

     anyMatch(LongPredicate predicate)

     noneMatch(LongPredicate predicate)

     LongStream 

     boolean 

     allMatch(DoublePredicate predicate)

     anyMatch(DoublePredicate predicate)

     noneMatch(DoublePredicate predicate)

     DoubleStream 




     다음 에제는 int[] 배열로부터 스트림을 생성하고, 모든 요소가 2의 배수인지, 하나라도 3의 배수가 존재하는지, 모든 요소가 3의 배수가 아닌지 조사합니다.



     * MatchExam.java


    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
     
    package stream;
     
    import java.util.Arrays;
     
    public class MatchExam {
     
        public static void main(String[] args) {
            int[] intArr = {24681012};
            
            boolean result = Arrays.stream(intArr)
                    .allMatch(a -> a % 2 == 0);
            System.out.println("모두 2의 배수 인가 ? " + result);
            
            result = Arrays.stream(intArr)
                    .anyMatch(a -> a % 3 == 0);
            System.out.println("하나라도 3의 배수가 있는가 ? " + result);
            
            result = Arrays.stream(intArr)
                    .noneMatch(a -> a % 5 == 0);
            System.out.println("5의 배수가 없는가 ? " + result);
        }
     
    }
     
    cs

















    9. 기본 집계(sum(), count(), average(), max(), min())

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


     집계는 대량의 데이터를 가공해서 축소하는 리덕션(Reduction) 이라고 볼 수 있습니다.


     



     9.1 스트림이 제공하는 기본 집계

      스트림은 다음과 같이 기본 집계 메소드를 제공하고 있습니다.




     리턴 타입

     메소드(파라미터) 

     설명 

     long 

     count() 

     요소 개수 

     OptionalXXX 

     findFirst() 

     첫 번째 요소 

     Optional<T>

     OptionalXXX 

     max(Comparator<T>)

     max() 

     최대 요소 

     Optional<T>

     OptionalXXX 

     min(Comparator<T>)

     min() 

     최소 요소 

     OptionalDouble 

     average() 

     요소 평균 

     int, long, double 

     sum() 

     요소 총합 





     이 집계 메소드에서 리턴하는 OptionalXX는 자바 8에서 추가한 java.util 패키지의 Optional, OptionalDouble, OptionalInt, OptionalLong 클래스 타입을 말합니다. 


     이들은 값을 저장하는 값 기반 클래스(value-based class)들 입니다.


     이 객체에서 값을 얻기 위해서는 get(), getAsDouble(), getAsInt(), getAsLong() 을 호출하면 됩니다.



     * AggregateExam.java



    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
    33
    34
     
    package stream;
     
    import java.util.Arrays;
     
    public class AggregateExam {
     
        public static void main(String[] args) {
            int[] intArr = {581113192024};
            long count = Arrays.stream(intArr)
                    .filter( n -> n % 2 == 0)
                    .count();
            System.out.println("2의 배수 개수: " + count);
            
            long sum = Arrays.stream(intArr)
                    .filter( n -> n % 2 == 0)
                    .sum();
            System.out.println("2의 배수의 합: " + sum);
            
            double avg = Arrays.stream(intArr)
                    .average()
                    .getAsDouble();
            System.out.println("배열의 평균; " + avg);
            
            int third = Arrays.stream(intArr)
                    .filter(n -> n % 3 == 0)
                    .findFirst()
                    .getAsInt();
            System.out.println("3의 배수: " + third);
            
        }
     
    }
     
    cs












     9.2 Optional 클래스

      Optional,  OptionalInt,  OptionalLong,  OptionalDouble 클래스에 대해서 좀 더 알아보기로 합시다.


      이 클래스들은 저장하는 값의 타입만 다를 뿐 제공하는 기능은 거의 동일합니다.


       Optional 클래스는 단순히 집계 값만 저장하는 것이 아니라, 집계 값이 존재하지 않을 경우 디폴트 값을 설정할 수도 있고, 집계 값을 처리하는 Consumer 도 등록할 수 있습니다. 다음은  Optional 클래스들이 제공하는 메소드들입니다.





     리턴 타입

     메소드(파라미터) 

     설명 

     boolean 

     isPresent() 

     값이 저장되어 있는지 여부 

     T

     double

     int 

     long 

     orElse(T)

     orElse(double)

     orElse(int)

     orElse(long) 

     값이 저장되어 있지 않을 경우 디폴트 값 지정 

     void

     ifPresent(Consumer)

     ifPresent(DoubleConsumer)

     ifPresent(IntConsumer)

     ifPresent(LongConsumer) 

     값이 저장되어 있을 경우 Consumer에서 처리 





      컬렉션의 요소는 동적으로 추가되는 경우가 많습니다. 만약 컬렉션의 요소가 추가되지 않아 저장된 요소가 없을 경우 다음 코드는 어떻게 될까요??



    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    List<Integer> list = new ArrayList<>();
    double avg = list.stream()
        .mapToInt(Integer :: intValue)
        .average()
        .getAsDouble();
     
    System.out.println("평균: " + avg);
     
    cs










      요소가 없기 때문에 평균값도 있을 수 없습니다. 그래서 NoSuchElementException 예외가 발생합니다.


      요소가 없을 경우 예외를 피하는 방법이 세 가지 방법이 있는데, 첫 번째는 Optional 객체를 얻어 isPresent() 메소드로 평균값 여부를 체크하는 것입니다. isPresent() 메소드가 true를 리턴할 때만 getAsDouble() 메소드로 평균값을 얻으면 됩니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    OptionalDouble optional = list.stream()
        .mapToInt(Integer :: intValue)
        .average();
     
    if (optional.isPresent()) {
        System.out.println("평균: " + optional.getAsDouble());
    else {
        System.out.println("평균 : " + 0.0);
    }
     
    cs




      두 번째 방법은 orElse() 메소드로 디폴트 값을 정해 놓습니다. 평균값을 구할 수 없는 경우에는 orElse() 의 파라미터값이 디폴트 값이 됩니다.



    1
    2
    3
    4
    5
    6
    double avg = list.stream()
        .mapToInt(Integer :: intValue)
        .average()
        .orElse(0.0);
     
    System.out.println("평균: " + avg);
    cs




      세 번째 방법은 ifPresent() 메소드로 평균값이 있을 경우에만 값을 이용하는 람다식을 실행합니다.


    1
    2
    3
    4
    5
    list.stream()
        .mapToInt(Integer :: intValue)
        .average()
        .ifPresent(a -> System.out.println("평균: " + a);
     
    cs





      * OptionalExam.java



    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
    33
    34
    35
    36
    37
     
    package stream;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.OptionalDouble;
     
    public class OptionalExam {
     
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            
            OptionalDouble optional = list.stream()
                    .mapToInt(Integer :: intValue)
                    .average();
            
            if (optional.isPresent()) {
                System.out.println("방법 1 평균: " + optional.getAsDouble());
            } else {
                System.out.println("방법 1 평균: " + 0.0);
            }
            
            double avg = list.stream()
                    .mapToInt(Integer :: intValue)
                    .average()
                    .orElse(0.0);
            
            System.out.println("방법 2 평균: " + avg);
            
            list.stream()
                .mapToInt(Integer :: intValue)
                .average()
                .ifPresent(a -> System.out.println("방법 3 평균: " + a));
        }
     
    }
     
    cs









    10. 커스텀 집계(reduce())

     스트림은 기본 집계 메소드인 sum(), average(), count(), max(), min()을 제공하지만, 프로그램화해서 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드를 제공합니다.




     인터페이스

     리턴 타입 

     메소드(파라미터) 

     Stream 

     Optional<T> 

     reduce(BinaryOperator<T> accumulator 

     T 

     reduce(T identity, BinaryOperator<T> accumulator) 

     IntStream 

     OptionalInt 

     reduce(IntBinaryOperator op) 

     int 

     reduce(int identity, IntBinaryOperator op) 

     LongStream 

     OptionalLong 

     reduce(LongBinaryOperator op) 

     long 

     reduce(long identity, LongBinaryOperator op) 

     Double Stream 

     OptionalDouble 

     reduce(DoubleBinaryOperator op) 

     double 

     reduce(double identity, DoubleBinaryOperator op) 





     각 인터페이스에서 매개 타입으로 XXXOperator, 리턴 타입으로 OptionalXXX, int, long, double을 가지는 reduce() 메소드가 오버로딩되어 있습니다.


     스트림에 요소가 전혀 없을 경우 디폴트 값인 identity 파라미터가 리턴됩니다.


     XXXOperator 파라미터는 집계 처리를 위한 람다식을 대입하는데, 예를 들어 학생들의 성적 총점은 학생 스트림으로 매핑해서 다음과 같이 얻을 수 있습니다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    int sum = studentList.stream()
        .map(Student :: getScore)
        .reduce( (a, b) -> a + b)
        .get();
     
    int sum2 = studentList.stream()
        .map(Student :: getScore)
        .reduce(0, (a, b) -> a + b);
     
    cs



     sum 코드는 스트림에 요소가 없을 경우 NoSuchElementException이 발생하지만, sum2 코드는 디폴트 값(identity)인 0을 리턴합니다. 


     스트림에 요소가 있을 경우에는 두 코드 모두 동일한 결과를 산출합니다.






     


     * 이 포스트은 서적 '이것이 자바다' 를 참고하여 작성한 포스트입니다.

    댓글

Designed by Tistory.