공부/이것이 자바다

[이것이 자바다] 16. 스트림과 병렬처리

규투리 2022. 9. 14. 19:01
반응형

목차

    1. 스트림(Stream)이란?

    자바의 스트림은 컬렉션에 저장되어 있는 엘리먼트들을 하나씩 순회하면서 처리할 수 있는 코드 패턴이다.

     

    1. 스트림의 특징

    1) 데이터 구조가 아닌 '데이터의 흐름'

    2) 데이터를 변경하지 않고 결과를 새로운 스트림에 저장

    3) 필요한 데이터만 메모리에 로드해 처리

    4) 데이터에 한 번만 접근

     

    그렇다면 스트림은 왜 사용할까?

     

    2. 스트림을 사용하는 이유

    1) 스트림은 배열이나 컬렉션(List, Set, Map)으로 원하는 값을 얻을 때 for 문 도배를 방지할 수 있다. 연속된 자료를 처리할 수 있는 인터페이스를 제공하기 때문에, 효과적으로 사용 가능하다.

    2) 람다식을 이용해서 코드의 양을 줄이고 간결하게 표현함으로써 코드의 가독성이 높아진다.

    3) 병렬처리가 가능하기 때문에, 많은 요소들을 빠르게 처리할 수 있다.

     

    스트림에 대한 내용은 크게 세 가지로 나눌 수 있다.

     

    전체 -> 맵핑 -> 필터링 1 -> 필터링 2 -> 결과 만들기 -> 결과물

     

    1. 선언

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

    2. 가공

    • Filtering
    • Mapping
    • Sorting
    • Iterating

    3. 반환

    • Calculating
    • Reduction
    • Collecting
    • Matching
    • Iterating

     

    이번 포스팅에서는 필터링, 매핑, 정렬, 루핑, 매칭, 집계 순으로 다루겠다.

     


     

    2. 필터링(distinct(), filter())

    필터링은 중간 처리 기능으로 요소를 걸러내는 역할을 한다.

     

    1. distinct()

    .distinct();

     

    중복 제거하는 메소드이다.

    Stream의 경우 Object.equals(Object) 가 true 이면 동일한 객체로 판단하고 중복을 제거한다.

     

    2. filter()

     

    Stream<T> filter(Predicate<? super T> predicate);

     

    매개값으로 주어진 Predictate 가 true를 리턴하는 요소만 필터링한다.

     


     

    3. 매핑(flatMapXXX(), mapXXX(), asXXXStream(), boxed())

    매핑(mapping)은 중간 처리 기능으로 스트림의 요소를 다른 요소로 대체하는 작업을 말한다.

    1. flatMapXXX()

    flatMapXXX() 메소드는 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴한다.

    다음 그림에서 A라는 요소는 A1, A2.로, B라는 요소는 B1, B2 로 대체된 후 "A1, A2, B1, B2" 를 가지는 새로운 스트림이 생성된 것이다.

    public static void main(String[] args){
            List<String> inputList1 = Arrays.asList("java8 lamda", "stream mapping");
            inputList1.stream()
                .flatMap(data -> Arrays.stream(data.split(" "))) // 요소별로 단어를 뽑아 스트림 재생성
                .forEach(System.out::println);
    
            System.out.println();
    }
    /*
    * java8
    * lamda
    * stream
    * mapping
    */

     

    2. mapXXX()

    mapXXX() 메소드는 요소를 대체하는 요소로 구성된 새로운 스트림을 리턴한다.

    다음 그림에서 A 요소는 C 요소로 대체되고, B 요소는 D 요소로 대체된다고 했을 경우 C, D 요소를 가지는 새로운 스트림이 생성된다.

     

    public static void main(String[] args){
            List<Worker> workerList = Arrays.asList(
                    new Worker("김규툴", 90),
                    new Worker("김자바", 80),
                    new Worker("김자반", 75)
            );
    
            workerList.stream()
                .mapToInt(Worker::getAge) // IntStream을 반환한다.
                .forEach(System.out::println);
    }
    // 90
    // 80
    // 75

     

    3. asDoubleStream(), asLongStream(), boxed()

    • asDoubleStream() : IntStream의 int 요소 또는 LongStream의 long 요소를 double 요소로 타입 변환해서 DoubleStream을 생성
    • asLongStream() : IntStream의 int 요소를 long 요소로 타입 변환해서 LongStream을 생성
    • boxed() : int, long, double 요소를 Integer, Long, Double 요소로 박싱해서 Stream을 생성

     


     

    4. 정렬(sorted())

    스트림은 요소가 최종 처리되기 전에 중간 단계에서 요소를 정렬해서 최종 처리 순서를 변경할 수 있다

    만약 객체를 기본 비교(Comparable) 방법으로 정렬하고 싶다면 아래와 같이 사용할 수 있다.

     

    sorted();
    sorted((a,b) -> a.compareTo(b));
    sorted(Comparator.naturalOrder());

     

    기본 비교(Comparable) 역순으로 정렬하고 싶다면 아래와 같이 사용

     

    sorted((a,b) -> b.compareTo(a));
    sorted(Comparator.reverseOrder());

     

    만약, 객체가 Comparable이 구현되어 있지않으면, Comparator를 매개값으로 갖는 sorted() 메소드를 사용하면 된다.

     

    sorted((a,b) -> { ... 구현 });

     


     

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

    루핑은 요소 전체를 반복하는 것을 말한다.

    • peek()
      • 중간 처리 메소드
      • 중간 처리 단계에서 전체 요소를 루핑하며 추가 작업하기 위해 사용
      • 최종처리 메소드가 실행되지 않으면 지연
        • 반드시 최종 처리 메소드가 호출되어야 동작
    • forEach()
      • 최종 처리 메소드
      • 파이프라인 마지막에 루핑하여 요소를 하나씩 처리
      • 요소를 소비하는 최종 처리 메소드
        • sum()과 같은 다른 최종 메소드 호출 불가

     

    public static void main(String[] args){
            int[] intArr = {1, 2, 3, 4, 5};
    
            // 최종처리 메소드가 없으면 동작하지 않음.
            Arrays.stream(intArr)
                .filter(a -> a%2 == 0)
                .peek(System.out::println); // peek은 중간 처리 메소드(intermediate)
    
            // 최종처리 메소드(Terminal) sum()이 존재하므로 정상 작동한다.
            Arrays.stream(intArr)
                .filter(a -> a%2 == 0)
                .peek(System.out::println)
                .sum();
    
            // forEach는 최종처리 메소드(Terminal) 이므로 아래는 정상 동작한다.
            Arrays.stream(intArr)
                .filter(a -> a%2 == 0)
                .forEach(System.out::println);
    }

     

     


     

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

    매칭이란 최종 처리 단계에서 요소들이 특정 조건에 만족하는지 조사하는 것이다.

    • allMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사
    • anyMatch() : 최소한 한 개의 요소가 매개값으로 주어진 Predicate조건을 만족하는지 조사
    • noneMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사

     

    public class MatchExample {
    	public static void main(String[] args) {
        	int[] intArr = {2, 4, 6};
            
            boolean result = Arrays.stream(intArr).allMatch(a -> a % 2 == 0);
            // 모든 만족 O
            System.out.println("모두 2의 배수인가?" + result);
            
            reslut = Arrays.stream(intArr).anyMatch(a -> a % 3 == 0);
            // 최소한 한개 만족
            System.out.println("하나라도 3의 배수가 있는가?" + result);
            
            reslut = Arrays.stream(intArr).noneMatch(a -> a % 3 == 0);
            // 모두 만족 X
            System.out.println("3의 배수가 없는가?" + result);
        }
    }
    // 모두 2의 배수인가?true
    // 하나라도 3의 배수가 있는가?true
    // 3의 배수가 없는가?false

     

     


     

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

    기본 집계 메소드인 sum(), average(), count(), max(), min() 말고도 커스텀하게 사용할 경우, Stream API 에서는 커스텀하기 집계 결과물을 만드는 reduce()라는 메소드를 제공한다.

     

    public static void main(String[] args){
            List<Worker> workerList = Arrays.asList(
                    new Worker("김규툴", 90),
                    new Worker("김자바", 80),
                    new Worker("김자반", 75)
            );
    
            // 집계함수 sum() 이용
            int sum1 = workerList.stream()
                    .mapToInt(Worker::getAge)
                    .sum();
    
            // custom한 집계함수 reduece 이용 => stream에 element가 없을시 오류 남
            int sum2 = workerList.stream()
                    .map(Worker::getAge)
                    .reduce((a,b) -> a+b)
                    .get();
    
            // reduce를 이용하여 identity(디폴트값 설정) => stream에 element가 없으면 0을 리턴함
            int sum3 = workerList.stream()
                    .map(Worker::getAge)
                    .reduce(0, (a,b) -> a+b);
    
            System.out.println("sum1 : " + sum1);
            System.out.println("sum2 : " + sum2);
            System.out.println("sum3 : " + sum3);
    }
    반응형