티스토리 뷰

한화시스템/백엔드

[BE] JAVA_Stream

jjam-mo 2024. 7. 29. 14:10

1. Stream 개요

1-1. Stream 이란?

💡 컬렉션에 저장된 엘리먼트들을 하나씩 순회하면서 처리할 수 있는 기능.
람다식과 함께 사용할 수 있으며 컬렉션에 들어있는 데이터에 대한 처리를 간결하게 표현

또한 내부 반복자를 사용하기 때문에 병렬처리가 쉽다는 장점.
package com.ohgiraffers.section01.intro;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Application1 {
    public static void main(String[] args) {

        /* 수업목표. Stream에 대해 이해하고 활용할 수 있다. */
        /* 설명.
        *   Arrays.asList(): 매개변수로 요소들을 전달하면 List로 반환
        *   ArrayList<>(Collection 타입): Collection 타입을 ArrayList 객체로 생성할 때 쓰이는 생성자
        *  */

        List<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "stream"));
        // .asList -> 가변 인자로 받고 반환형은 해당 타입의 List로 반환

        System.out.println("====== foreach");
        for(String str: stringList) {
            System.out.println(str);
        }

        System.out.println("====== stream");
//        stringList.stream().forEach(x -> System.out.println(x));  // stream으로 바뀐 ArrayList의 요소들이 각각
                                                                    // 람다식에 적용되어 동작한다.

        /* 필기. 복습하자!! */
        stringList.stream().forEach(System.out::println);       // 이런 코드 많이 쓴다. 이거 보고 위의 것처럼 인식할 수 있어야한다!
        // 아직 시간복잡도 상 O(N)으로 효율이 생기진 않았다.
    }
}

// 실행 결과
====== foreach
hello
world
stream
====== stream
hello
world
stream

1-2. Stream이 필요한 이유

  • 배열 또는 컬렉션을 함수 여러 개를 사용해서 결과를 쉽게 얻을 수 있다.
  • 람다식을 활용해 코드 양도 줄이고 간결하게 표현도 가능.
  • 또 하나의 장점은 병렬처리가 가능.

package com.ohgiraffers.section01.intro;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Application2 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 병렬처리에 대해 이해할 수 있다. */
        List<String> stringList =
                new ArrayList<>(Arrays.asList("java", "mariadb", "jdbc", "html", "css"));

        /* 설명. main 쓰레드 상에서 스트림을 사용하지 않고 확인 */
        System.out.println("====== foreach");
        for(String s: stringList) {
            System.out.println(s + ": " + Thread.currentThread().getName());
        }

        /* 설명. main 쓰레드 상에서 단순 스트림을 사용하고 확인 */
        /* 필기. forEach(): 각각의 요소에 대해서 반환형이 없이 그냥 실해하기 위해 사용 */
        System.out.println("====== normal stream");
        stringList.stream().forEach(System.out::println);   // 잘 동작하는지 그냥 확인
//        stringList.stream().forEach(x -> Applcation2.print(x));
        stringList.stream().forEach(Application2::print);

        /* 설명. main 쓰레드 상에서 병렬 스트림을 사용하고 확인(main 쓰레드 외의 다른 쓰레드를 활용한다.) */
        /* parallel을 사용해야 실제로 병렬처리하면서 효율적이다. */
        System.out.println("====== parallel stream");
        stringList.parallelStream().forEach(Application2::print);
    }

    private static void print(String s) {
        System.out.println(s + ": " + Thread.currentThread().getName());
        System.out.println(s + ": " + Thread.currentThread().getName());
        System.out.println(s + ": " + Thread.currentThread().getName());
    }
}

// 실행 결과
====== foreach
java: main
mariadb: main
jdbc: main
html: main
css: main
====== normal stream
java
mariadb
jdbc
html
css
java: main
java: main
java: main
mariadb: main
mariadb: main
mariadb: main
jdbc: main
jdbc: main
jdbc: main
html: main
html: main
html: main
css: main
css: main
css: main
====== parallel stream
jdbc: main
jdbc: main
jdbc: main
mariadb: ForkJoinPool.commonPool-worker-1
mariadb: ForkJoinPool.commonPool-worker-1
mariadb: ForkJoinPool.commonPool-worker-1
html: ForkJoinPool.commonPool-worker-2
html: ForkJoinPool.commonPool-worker-2
html: ForkJoinPool.commonPool-worker-2
css: main
css: main
css: main
java: ForkJoinPool.commonPool-worker-4
java: ForkJoinPool.commonPool-worker-4
java: ForkJoinPool.commonPool-worker-4

1-3. Stream 특징

  1. 원본을 변경하지 않는 읽기 전용.
  2. `iterator` 처럼 한번만 사용되고 사라짐. 필요하면 다시 스트림을 생성해야 한다.
  3. 최종 연산 전까지 중간 연산이 처리되지 않는다.
  4. 병렬 처리가 용이하다.

2. Stream 활용

  1. 생성 : 스트림 생성
  2. 가공 : 원하는 결과를 만들기 위한 필터링, 매핑 등의 작업
  3. 결과 : 최종 결과를 만들어 내는 작업
배열, 컬렉션 → 스트림 생성 → 매핑 → 필터링 → 결과

2-1. Stream 생성

💡 자주 사용하는 배열과 컬렉션 객체에서 `stream()` 메소드를 지원.

2-1-1. 배열 스트림 생성

  • 스트림을 생성할 배열을 생성한 후 `stream()` 사용.

2-1-2. 컬렉션 스트림 생성

  • 컬렉션도 마찬가지로 `stream()`을 사용.

2-1-3. 비어 있는 스트림

  • 비어있는 스트림도 생성할 수 있는데, 요소가 없을 떄 사용.

2-1-4. Stream.builder()

  • 빌더를 사용해 직접 값을 넣을 수도 있다.

2-1-5. Stream().iterate()

  • `iterate()` 메소드를 사용하여 수열 형태로 스트림을 생성.

2-1-6. 스트림 생성 - 기본 타입

  • 스트림을 제네릭을 사용하지 않고 기본 타입으로 스트림을 생성할 수 있다.
  • 제네릭을 사용하지 않기 때문에 불필요한 오토박싱도 일어나지 않는다.
  • 필요한 경우에는 `boxed()`를 사용해 박싱을 할 수도 있다.

2-1-7. 문자 관련 스트림

2-1-8. 스트림 합치기

package com.ohgiraffers.section02.uses.subsection01.generation;

import java.util.Arrays;
import java.util.stream.Stream;

public class Application1 {
    public static void main(String[] args) {

        /* 수업목표. 배열이나 컬렉션은 스트림을 이용할 수 있고 이를 이해해서 활용할 수 있다. */
        String[] sArr = new String[] {"java", "mariadb", "jdbd"};

        /* 필기. Arrays.stream(배열): 배열 자료형을 Stream 자료형으로 변환 */
        /* 설명. 1. 배열로 스트림 생성 */
        System.out.println("======= 배열로 스트림 생성 =======");
        Stream<String> strStream1 = Arrays.stream(sArr);
        strStream1.forEach(System.out::println);

        System.out.println();       // 구분을 위한 개행

        Stream<String> strStream2 = Arrays.stream(sArr, 0, 2);
        strStream2.forEach(System.out::println);
        System.out.println();

        /* 설명. 2.
        *   Builder를 활용한 스트림 생성
        *   builder는 static<T>로 되어 있는 메소드이며, 호출 시 타입 파라미터를 메소드 호출 방식으로 전달한다.
        *  */
        System.out.println("======= Builder로 스트림 생성 =======");
        Stream<String> builderStream = Stream.<String>builder()     // 메소드를 호출할 때 제네릭을 사용. 그냥 받아들이자...
                                            .add("홍길동")
                                            .add("유관순")
                                            .add("윤봉길")
                                            .build();
        // 남용하지 말고! 고급 기술 사용 시 알고 쓰자! 면접 떄 공격 들어온다!~

        builderStream.forEach(System.out::println);

        /* 설명. 3. iterate()를 활용하여 수열 형태의 스트림을 생성 */
        System.out.println("======== iterate로 스트림 생성 ========");
        Stream<Integer> intStream = Stream.iterate(10, value -> value * 2)
                                            .limit(10);
        intStream.forEach(value -> System.out.print(value + " "));

//        // 이렇게도 가능하다.
//        Stream.iterate(10, value -> value * 2)
//                .limit(10).forEach(value -> System.out.print(value + " "));

    }
}

// 실행 결과
======= 배열로 스트림 생성 =======
java
mariadb
jdbd

java
mariadb

======= Builder로 스트림 생성 =======
홍길동
유관순
윤봉길
======== iterate로 스트림 생성 ========
10 20 40 80 160 320 640 1280 2560 5120
package com.ohgiraffers.section02.uses.subsection01.generation;

import java.util.Random;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class Application2 {
    public static void main(String[] args) {

        /* 수업목표. 기본 타입 스트림 생성에 대해 이해하고 활용할 수 있다. */
        /* 필기.
        *   range(시작값, 종료값): 시작값부터 1씩 증가하는 숫자로 종료값 직전(-1)까지 범위의 스트림 생성
        *   rangeClosed(시작값, 종료값): 시작값부터 1씩 증가하는 숫자로 종료값까지 포함한 스트림 생성
        *  */
        IntStream intStream = IntStream.range(5, 10);
        intStream.forEach(value -> System.out.print(value + " "));
        System.out.println();

        LongStream longStream = LongStream.rangeClosed(5, 10);      // 파이썬에도 가능? 나중에 꼭 사용해보자!
        longStream.forEach(value -> System.out.print(value + " "));
        System.out.println();

        /* 필기.
        *   Wrapper 클래스 자료형의 스트림이 필요한 경우 boxing도 가능하다.
        *   double(갯수): 난수를 활용한 DoubleStream을 갯수만큼 생성하여 반환한다.
        *   boxed(): 기본 타입 스트림인 XXXStream을 박싱하여 Wrapper 타입의 Stream<XXX>로 변환한다.
        *  */
        Stream<Double> doubleStream = new Random().doubles(5).boxed();
        doubleStream.forEach(value -> System.out.print(value + " "));
        System.out.println();

        /* 설명. 문자열을 split하여 Stream으로 생성 */
        // 그냥 통짜로 보자!
        Stream<String> splitStream = Pattern.compile(", ").splitAsStream("html, css, javascript");
        splitStream.forEach(System.out::println);

    }
}

// 실행 결과
5 6 7 8 9 
5 6 7 8 9 10 
0.49334622605269485 0.9065024657621377 0.9595659744642123 0.6629202467254909 0.14572468272728478 
html
css
javascript

 

2-2. Stream 가공

💡 스트림에 있는 데이터에 대해 내가 원하는 결과를 만들기 위해서 중간 처리 작업 

2-2-1. Filter

  • 스트림에서 특정 데이터만 걸러내는 메소드
  • 매개변수로 받는 `Predicate`는 `boolean`을 리턴하는 함수형 인터페이스.
package com.ohgiraffers.section02.uses.subsection02.intermediate;

import java.util.stream.IntStream;

public class Application1 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 중계 연산 중 하나인 filter에 대해 이해하고 사용할 수 있다. */
        /* 필기. 중계 연산(intermediate operator): Stream을 반환하며 Stream 관련 메소드 체이닝이 이어진다. */

        /* 설명. 필터(filter)는 스트림에서 특정 데이터만을 걸러내기 위한 메소드이다. */
        IntStream intStream = IntStream.range(0, 10);
        intStream.filter(i -> i % 2 == 0)       // 쓸 수 있는 능력을 갖춰야한다!
                .forEach(i -> System.out.print(i + " "));
    }
}

// 실행 결과
0 2 4 6 8

2-2-2. Map

  • `map()` 메소드는 값을 가공할 수 있는 람다식을 매개변수로 받는다.
  • 스트림에 들어있는 데이터를 특정 람다식을 통해 데이터를 가공하고 새로운 스트림에 담아주는 역할
package com.ohgiraffers.section02.uses.subsection02.intermediate;

import java.util.stream.IntStream;

public class Application2 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 중계 연산 중 하나인 map에 대해 이해하고 사용할 수 있다. */
        /* 설명. 맵(map)은 스트림에 들어있는 데이터를 람다식으로 가공하고 새로운 스트림에 담아주는 메소드이다. */
        IntStream intStream = IntStream.range(1, 10);
        intStream.filter(i -> i % 2 != 0)               // boolean 반환형으로 람다식 작성(predicate)
                .map(i -> i * 5)                        // 요소를 반환하는 형태로 람다식 작성(operator)
                .forEach(result -> System.out.print(result + " ")); // 반환형이 없는 람다식 작성(consumer)
    }
}

// 실행 결과
5 15 25 35 45

2-2-3. flatMap

  • 중첩 구조를 한 단게 제거하고 단일 컬렉션으로 만들어준다.
package com.ohgiraffers.section02.uses.subsection02.intermediate;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class Application3 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 중계 연산 중 하나인 flatMap에 대해 이해하고 사용할 수 있다. */
        List<List<String>> list = Arrays.asList(
                                        Arrays.asList("JAVA", "SPRING", "SPRINGBOOT"),
                                        Arrays.asList("java", "spring", "springboot")
                                );

        list.stream().forEach(System.out::println);
        System.out.println("list = " + list);

        List<String> flatList = list.stream().flatMap(Collection::stream)
                                            .collect(Collectors.toList());
        flatList.stream().forEach(System.out::println);
        System.out.println("flatList = " + flatList);       // depth가 깊어져도 하나의 리스트로 만들어준다.
    }
}

// 실행 결과
[JAVA, SPRING, SPRINGBOOT]
[java, spring, springboot]
list = [[JAVA, SPRING, SPRINGBOOT], [java, spring, springboot]]

JAVA
SPRING
SPRINGBOOT
java
spring
springboot
flatList = [JAVA, SPRING, SPRINGBOOT, java, spring, springboot]

2-2-4. Sorting

  • 데이터를 정렬
package com.ohgiraffers.section02.uses.subsection02.intermediate;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Application4 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 중계 연산 중 하나인 sorted에 대해 이해하고 사용할 수 있다. */
        List<Integer> integerList = IntStream.of(5, 10, 99, 2, 1, 35)
                                            .boxed()        // sorted를 사용하기 위해 int 형으로 바꿔준다.
//                                            .sorted()
                                            .sorted(new DescInteger())
                                            .collect(Collectors.toList());

        System.out.println("정렬 된 Integer List: " + integerList);
    }
}

// 실행 결과
정렬 된 Integer List: [99, 35, 10, 5, 2, 1]
package com.ohgiraffers.section02.uses.subsection02.intermediate;

import java.util.Comparator;

public class DescInteger implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;     // 매개변수와 순서를 바꾸면 내림차순
    }
}

2-3. Stream 결과

💡 데이터를 필터링하고 가공한 뒤에 출력하기 위해서 진행하는 작업

2-3-1. Calculating

  • 다양한 메소드들을 제공.
  • 최소/최대/총합/평균 등
  • 만약 스트림이 비어 있으면 count와 sum은 0을 출력
package com.ohgiraffers.section02.uses.subsection03.terminal;

import java.util.OptionalInt;
import java.util.stream.IntStream;

public class Application1 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 최종 연산(terminal Operator) 중 하나인 calculation에 대해 이해하고 사용할 수 있다. */
        /* 필기. 최종 연산: Stream이 아닌 값을 반환하며 Stream이 끝날 때 사용한다. */
        long count = IntStream.range(1, 10).count();
        int sum = IntStream.range(1, 10).sum();

        System.out.println("count = " + count);
        System.out.println("sum = " + sum);

        /* 설명. OptionalInt는 '결과 없음'을 나타내야 하는 명확한 요구가 있는 메소드 반환 형식으로 사용하기 위한 타입이다. */
        OptionalInt max = IntStream.range(1, 10).max();
//        OptionalInt max = IntStream.range(1, 1).max();      // OptionalInt: 기본자료형도 존재하지 않으면
//                                                            //              empty의 개념을 주기위한 타입
        OptionalInt min = IntStream.range(1, 10).min();

        System.out.println("max = " + max);
        System.out.println("min = " + min);

        int oddSum = IntStream.range(1, 10)
                            .filter(i -> i % 2 == 1)
                            .sum();
        System.out.println("oddSum = " + oddSum);

    }
}

// 실행 결과
count = 9
sum = 45
max = OptionalInt[9]
min = OptionalInt[1]
oddSum = 25

2-3-2. Reduction

  • `reduce()` 라는 메소드는 스트림에 있는 데이터들의 총합을 계산
  • 파라미터에 따라 3가지 종류가 있다.
package com.ohgiraffers.section02.uses.subsection03.terminal;

import java.util.OptionalInt;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Application2 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 최종 연산 중 하나인 reduce에 대해 이해하고 사용할 수 있다. */

        /* 설명. 인자가 1개일 경우 */
        OptionalInt reduceOneParam = IntStream.range(1, 4)
                                            .reduce((a, b) -> Integer.sum(a, b));   // reduce -> 누적을 시킨다!

        System.out.println("reduceOneParam = " + reduceOneParam.getAsInt());

        /* 설명. 인자가 2개일 경우 */
        int reduceTwoParam = IntStream.range(1, 4)
                                    .reduce(100, Integer::sum);     // identity부터 누적 시작
                                                      // Integer::sum == (a, b) -> Integer.sum(a, b)
        System.out.println("reduceTwoParam = " + reduceTwoParam);

        /* 설명. 인자가 3개일 경우 */
        /* 설명. 매개변수 3번째는 좀 더 효율적인 가산 누적연산을 위한 충간 합계 처리용 람다식을 작성한다.(2번째 인자의 결과와 호환이 가능해야 한다.) */
        Integer reduceThreeParam = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                                        .reduce(100, Integer::sum, (x, y) -> x + y);

        System.out.println("reduceThreeParam = " + reduceThreeParam);

    }
}

// 실행 결과
reduceOneParam = 6
reduceTwoParam = 106
reduceThreeParam = 155

2-3-3. Collecting

  • `collect()`는 Collector 타입을 받아서 처리, 해당 메소드르 통해 컬렉션을 출력으로 받을 수 있다.
  • `collect()` 메소드는 Collector 객체에서 제공하는 정적 메소드를 사용할 수 있다.
  1. Collectors.toList()
    : 스트림 작업 결과를 리스트로 반환
  2. Collectors.joining()
    : 스트림의 작업 결과를 String 타입으로 이어 붙인다.
    : 세 개의 인자를 받을 수 있다. → delimiter(구분자), prefix(맨 앞 문자), suffix(맨 뒤 문자)
package com.ohgiraffers.section02.uses.subsection03.terminal;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Application3 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 최종 연산 중 하나인 Collect에 대해 이해하고 사용할 수 있다. */
        /* 필기.
        *   collect()는 Collector 객체에서 제공하는 정적(static)메소드를 사용할 수 있고 해당 메소드들을 통해
        *   일반적으로 컬렉션(list, Set, Map)을 출력으로 받을 수 있다.
        *  */
        List<Member> memberList = Arrays.asList(
                new Member("test01", "testName01"),
                new Member("test02", "testName02"),
                new Member("test03", "testName03")
        );

        List<String> collectorCollection = memberList.stream()
                                                    .map(Member::getMemberName)     // 중계 연산
                                                    .collect(Collectors.toList());  // 최종 연산

        collectorCollection.forEach(System.out::println);

        /* 설명. joining()은 요소들을 하나로 합쳐서 하나의 문자열로 바꿔주는 메소드이다. */
        String str = memberList.stream()
                                .map(Member::getMemberName)
                                .collect(Collectors.joining());
        System.out.println("str = " + str);

        String str2 = memberList.stream()
                                .map(Member::getMemberName)
                                .collect(Collectors.joining("||", "**", "!!"));
        System.out.println("str2 = " + str2);

    }
}

// 실행 결과
testName01
testName02
testName03

str = testName01testName02testName03

str2 = **testName01||testName02||testName03!!
package com.ohgiraffers.section02.uses.subsection03.terminal;

public class Member {
    private String memberId;
    private String memberName;

    public Member() {
    }

    public Member(String memberId, String memberName) {
        this.memberId = memberId;
        this.memberName = memberName;
    }

    public String getMemberId() {
        return memberId;
    }

    public void setMemberId(String memberId) {
        this.memberId = memberId;
    }

    public String getMemberName() {
        return memberName;
    }

    public void setMemberName(String memberName) {
        this.memberName = memberName;
    }

    @Override
    public String toString() {
        return "Member{" +
                "memberId='" + memberId + '\'' +
                ", memberName='" + memberName + '\'' +
                '}';
    }
}

2-3-4. Matching

  • Predicate를 인자로 받아 조건을 만족하는 엘리먼트가 있는지 확인하고 `boolean` 으로 리턴
package com.ohgiraffers.section02.uses.subsection03.terminal;

import java.util.Arrays;
import java.util.List;

public class Application4 {
    public static void main(String[] args) {

        /* 수업목표. 스트림의 최종 연산 중 하나인 match에 대해 이해하고 사용할 수 있다. */
        List<String> stringList = Arrays.asList("Java", "Spring", "SpringBoot");

        boolean anyMatch = stringList.stream()      // 하나라도 만족하면 true
                                    .anyMatch(str -> str.contains("p"));
        boolean allMatch = stringList.stream()      // 모두 만족하면 true
                                    .allMatch(str -> str.length() > 4);
        boolean noneMatch = stringList.stream()     // 하나도 만족하지 않으면 true
                                    .noneMatch(str -> str.contains("c"));

        System.out.println("anyMatch = " + anyMatch);
        System.out.println("allMatch = " + allMatch);
        System.out.println("noneMatch = " + noneMatch);
    }
}

// 실행 결과
anyMatch = true
allMatch = false
noneMatch = true

'한화시스템 > 백엔드' 카테고리의 다른 글

[BE] Spring AOP  (0) 2024.08.12
[BE] JAVA_ENUM  (0) 2024.07.24
[BE] JAVA_컬렉션(Collection)_Map  (2) 2024.07.24
[BE] JAVA_컬렉션(Collection)_Set  (1) 2024.07.24
[BE] JAVA_컬렉션(Collection)_List  (1) 2024.07.23
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
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
글 보관함