DEV/Java

[java] 스트림 도전기 (3 / 3) - 스트림 생성 ~ 연산까지

Bi3a 2023. 10. 26. 13:37

728x90

java 기초 깨부시기

 

Table of Contents

    0. 이전 관련 포스팅

     

    [java 기초] 람다와 스트림, 스트림의 이해와 생성(1 / 3)

    Table of Contents 주의 :: 주인장 학습용 포스팅입니다. Stream API : 이론 학습에 어질어질한 파트일 수 밖에 없습니다.. Stream API : JDK 8때 도입되어 객체지향적 언어인 Java를 함수형으로 프로그래밍 할

    doinitright.tistory.com

     

     

    [java 기초] 람다와 스트림(2 / 3) - 람다식, java.util.function

    Table of Contents 이전 포스팅에서 이어집니다. [java 기초] 람다와 스트림, 스트림의 이해와 생성(1 / 3) Table of Contents 주의 :: 주인장 학습용 포스팅입니다. Stream API : 이론 학습에 어질어질한 파트일

    doinitright.tistory.com


    오늘 포스팅 이후로, 스트림을 쓰고 말 것이다..

     

     

    스트림의 생성

    컬렉션 인터페이스 클래스

    `stream` 메소드로 생성
    * 주요 클래스 : `List, ArrayList, LinkedList, Vector, Stack, Queue, Set, HashSet, TreeSet`
    ArrayList<String> list = new ArrayList<>(List.of("Hello", "World"));
    Stream<String> listStream = list.stream();
    
    Queue<String> queue = new LinkedList<>(List.of("Hello", "World"));
    Stream<String> queueStream = queue.stream();
    
    TreeSet<String> set = new TreeSet<>(List.of("Hello", "World"));
    Stream<String> setStream = set.stream();

     

    Java Collecion과 인터페이스에 대한 이해
    (참고) : java Collection 인터페이스의 구조

     

    배열

    `Arrays.stream` 메소드로 생성
    String[] arr = new String[]{"Hello", "World"};
    Stream<String> arrStream = Arrays.stream(arr);

     

     

    기본 타입 특화 스트림 (int, long, double)

    `IntStream, LongStream, DoubleStream` 클래스로 생성
    IntStream intStream = IntStream.range(0, 4); // 0, 1, 2, 3
    DoubleStream doubleStream = DoubleStream.of(1, 3, 5); // 1.0, 3.0, 5.0
    LongStream longStream = LongStream.rangeClosed(0, 4); // 0, 1, 2, 3, 4

     

    Pattern 생성

    `Pattern.complie`
     Stream<String> patternStream = Pattern.compile("챔피언")
            .splitAsStream("챔피언 소리지르는 니가 챔피언 음악에 미치는 니가 챔피언 ");
    patternStream.forEach(System.out::println); // out : 소리지르는 니가 \n 음악에 미치는 니가 \n

     

    iterate : 초기값을 인수로 받아 규칙적인 값을 생성

    `iterate` / `limit` 로 break를 걸어 크기 제한
    IntStream iterateStream = IntStream.iterate(3, x -> x + 3).limit(3); 
    // out : 3, 6, 9

     

    generate : 초기값, 인수 없이 일정한 값 생성 

    `generate` / `limit` 로 break를 걸어 크기 제한
    IntStream generateStream = IntStream.generate(() -> 10).limit(2);
    // out : 10, 10

     

    concat : 두 스트림 연결

    `concat` : 두 스트림을 연결합니다.
    String[] arr = new String[]{"Hello", "World"};
    Stream<String> arrStream = Arrays.stream(arr);
    
    Queue<String> queue = new LinkedList<>(List.of("Hello", "World"));
    Stream<String> queueStream = queue.stream();
    
    Stream<String> concatStream = Stream.concat(arrStream, queueStream);
    concatStream.forEach(System.out::println); // Hello \n World \n Hello \n World

     


     

    스트림의 중간 연산

    filter : 특정한 조건으로 필터링 

    Predicate<T> 함수형 인터페이스를 활용해 특정 조건에 맞는 스트림 내 요소들을 필터링합니다.
    ArrayList<String> list = new ArrayList<>(List.of("Hello", "World"));
    Stream<String> listStream = list.stream();
    Queue<String> queue = new LinkedList<>(List.of("Hello", "World"));
    Stream<String> queueStream = queue.stream();
    
    listStream.filter(s -> s.contains("H")).forEach(System.out::println); // out : Hello
    queueStream.filter(s -> s.length() == 5).forEach(System.out::println); // out : Hello, World

     

    map : 특정한 형태로 변환 

    Function<T> 함수형 인터페이스를 활용해 스트림 내 요소들을 특정 형태로 변환시킵니다.
    TreeSet<String> set = new TreeSet<>(List.of("Hello", "World"));
    Stream<String> setStream = set.stream();
    setStream.map(String::toLowerCase).forEach(System.out::println); // out : hello \n world
    
    String[] arr = new String[]{"Hello", "World"};
    Stream<String> arrStream = Arrays.stream(arr);
    arrStream.map(s -> s + "!").forEach(System.out::println); // out : Hello! \n World!

     

    maptoInt / Long / Double : 기본 타입 특화 스트림 변환 

    해당 스트림을 특정 기본 타입으로 매핑합니다.
    기본 데이터 타입에 특화된 스트림 변환이 가능하며,
    박싱 비용 절감 효과가 있습니다.
    `intStream은 mapToInt` / `LongStream은 mapToLong` / `DoubleStream은 mapToDouble` 사용불가
    int[] intArr = new int[]{1, 2, 3, 4, 5};
    IntStream intStream1 = Arrays.stream(intArr);
    intStream1.filter(i -> i < 4).max().stream().
    mapToDouble(v -> v+1).forEach(System.out::println); // out : 4.0
    
    String[] strArr = new String[]{"1", "3", "5", "7", "9"};
    int max = Arrays.stream(strArr).mapToInt(Integer::parseInt).max().getAsInt();
    System.out.println(max); // out : 9

    max()의 경우 단일의 값을 리턴하므로 getAsInt()를 통해 int값을 리턴받습니다.

     

     

    flatMap: N차원 배열, 리스트 등을 단일 차원 구조로 변환

    n차원으로 선언된 배열, n차원 리스트 등 중첩되어 있는 구조를 없애고 단일 구조로 변환시킵니다.
    List<String> list1 = List.of("Alpha", "Beta");
    List<String> list2 = List.of("Charlie", "Delta");
    List<List<String>> list3 = List.of(list1, list2);
    System.out.println(list3); // out : [Alpha, Beta], [Charlie, Delta]
    
    List<String> list4 = list3.stream().flatMap(Collection::stream).toList();
    System.out.println(list4); // out : Alpha, Beta, Charlie, Delta

     

     

    sorted : 정렬하기

    stream 내부 요소를 정렬합니다.
    `sorted` 내부에는 `comparator, comparable` 을 활용한 정렬 패턴 설정 및 역순 정렬 등이 가능합니다.
    `IntStream, DoubleStream, LongStream` 기본형 스트림은 `.boxed()` 로 래퍼 클래스 스트림 변환 후 사용합니다.
    List<String> sortedList = 
    List.of("Alpha", "Delta", "Charlie", "Beta").stream().sorted().toList();
    System.out.println(sortedList); // out : Alpha, Beta, Charlie, Delta
    
    List<Integer> sortedIntList = 
    IntStream.range(0, 10).boxed().sorted(Comparator.reverseOrder()).toList();
    System.out.println(sortedIntList); // out : 9, 8, 7, 6, 5, 4, 3, 2, 1, 0

     

     

    peek : 중간 연산 결과를 중간 확인

    스트림의 중간 요소에 대한 연산을 수행합니다.
    연산 과정에서 요소를 소모하지 않으므로 통상 중간 연산 사이의 결과를 확인할 때 많이 사용됩니다.
    최종 연산이 실행되지 않으면 `peek`를 사용할 수 없습니다.
    double d = IntStream.range(0, 5)
            .filter(i -> i < 4)
            .mapToDouble(i -> i + 1)
            .peek(i -> System.out.print("중간: " + i + " ")) 
            .max().orElse(-1); // 최종 연산 없을 시 peek 에러
    System.out.println("최종: "+ d); // out : 중간: 1.0 중간: 2.0 중간: 3.0 중간: 4.0 최종: 4.0

     

     

    limit / skip : 특정 요소 생략 /  개수 제한 하기

    `skip` : 스트림 내 특정 요소를 일정 개수만큼 건너뛰는 연산을 수행합니다.
    List<String> skipList = List.of("Alpha", "Beta", "Charlie", "Delta")
                    .stream().skip(2).toList();
    System.out.println(skipList); // out : Charlie, Delta

     

    `limit` : 일정 수만큼 스트림 내 요소의 개수를 제한하는 연산을 수행합니다.
    List<String> limitList = List.of("Alpha", "Beta", "Charlie", "Delta")
            .stream().limit(2).toList();
    System.out.println(limitList); // out : Alpha, Beta

     


     

    스트림의 최종 연산

    forEach, forEachOrdered : 요소 순회

    `forEach` : 요소를 순회합니다. (병렬 스트림에서 순서 보장 X)
    `forEachOrdered` : 병렬 스트림에 사용합니다. (병렬 스트림에서 순서 보장 O)
    List.of("눈","누","난","나").parallelStream().forEach(System.out::println);
    // out : 난 \n 나 \n 눈 \n 누 (순서 보장 X)
    
    List.of("눈","누","난","나").parallelStream().forEachOrdered(System.out::println);
    // out : 눈 \n 누 \n 난 \n 나 (순서 보장 O)

     

     

    reduce : 결과 합치기

    연산 규칙을 정의해 스트림 요소를 정의하여 결과를 합칩니다.
    reduce는 총 3가지 형태를 가지고 있으며 각 형태마다 다른 연산을 정의할 수 있습니다.
    1개의 인자를 받는 형태 : 인자로 `BinaryOperator` 를 사용합니다.
    `BinaryOperator` : 두 개의 동일 타입 요소 → 동일한 타입의 결과를 리턴합니다. 
    int reduce1 = IntStream.rangeClosed(1, 5).reduce((a, b) -> a + b).getAsInt()
    System.out.println(reduce1); // out : 15 (1 + 2 + 3 + 4 + 5)

     

    2개의 인자를 받는 형태 : 인자로 `BinaryOperator` 와 `항등값` 을 사용합니다.
    `BinaryOperator` : 두 개의 동일 타입 요소 → 동일한 타입의 결과를 리턴합니다. 
    `항등값` : 항상 동일한 값입니다.
    int reduce2 = IntStream.rangeClosed(1, 5).reduce(3, (a, b) -> a + b);
    System.out.println(reduce2); // out : 18 (3 , 1 + 2 + 3 + 4 + 5)

     

     

    3개의 인자를 받는 형태 : 인자로 `BinaryOperator` 와 `항등값`, 'BiFunction' 을 사용합니다.
    `BinaryOperator` : 두 개의 동일 타입 요소 → 동일한 타입의 결과를 리턴합니다. 
    `항등값` : 항상 동일한 값입니다.
    `BiFunction` : 값 누계 연산에 사용합니다.
    int num1 = 5, num2 = 10, num3 = 15;
    int result = Stream.of(num1, num2, num3)
            .reduce(0, (acc, value) -> acc + value, Integer::sum);
                // 초기값 : 0, 첫번째 인자 5
                // acc, value : 현재까지 누산된 결과 acc와 스트림 각 요소 value를 더함
                // Integer::sum : 누산된 결과를 더함
    
                // 0 + 5 = 5
                // 5 + 10 = 15
                // 15 + 15 = 30
    System.out.println(result); // out : 30

     

    * 일반적으로 3개의 인자를 받는 형태의 연산은 병렬 스트림에서 사용됩니다.

    * 일반 스트림과 병렬 스트림의 연산 값이 상이합니다.

     

     

    min, max, average, count : 한 개의 결과 리턴(최소, 최대, 평균, 개수 등)

    기본 산술 연산을 제공합니다.
    IntStream, DoubleStream, LongStream 등에서 동작하거나,
    다른 stream은 연산을 위해 해당 원시타입 (int, long, double) 으로 매핑이 필요합니다.
    ArrayList<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5));
    
    // count
    System.out.println(list.stream().count()); // out : 5
    
    // max
    System.out.println(list.stream().mapToInt(i->i).max().getAsInt()); // out : 5
    
    // min
    System.out.println(list.stream().mapToInt(i->i).min().getAsInt()); // out : 1
    
    // average
    System.out.println(list.stream().mapToDouble(i->i).average().getAsDouble()); // out : 3.0

     

    anyMatch, allMatch, noneMatch : 요소의 참, 거짓 조건 확인

    `anyMatch` : 하나라도 조건을 만족하는 요소가 있으면 true / 아니면 false를 리턴합니다.
    `allMatch` : 모든 요소가 조건을 만족하면 true / 아니면 false를 리턴합니다.
    `noneMatch` : 모든 요소가 조건을 만족하지 않으면 true / 아니면 fasle를 리턴합니다.

     

    ArrayList<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5));
    
    // anyMatch
    System.out.println(list.stream().anyMatch(i -> i < 1)); // out : false
    
    // allMatch
    System.out.println(list.stream().allMatch(i -> i > 0)); // out : true
    
    // noneMatch
    System.out.println(list.stream().noneMatch(i -> i > 5)); // out : true​

     

     

    Collectors : 분량 조절 실패로 다음 포스팅에서 이어집니다.

    제길 졸업하지 못했어!

     

     


    # 구현 코드

    깃허브로 이동합니다.