Stream이란? 

java에서 데이터의 흐름을 생성할 수 있습니다. 이러한 흐름을 Stream이라고 합니다. Stream을 생성하고 데이터에 연산을 진행할 수 있습니다. 

 

1. 중간 연산 -> 마지막이 아닌 위치에서 실행되는 연산

2. 최종 연산 -> 마지막에 진행되는 연산

 

연산들을 이용해서 데이터를 가공하는 것이 Stream의 목표라고 할 수 있습니다.


Stream 생성

Stream을 생성하는 여러 가지 방법을 살펴보겠습니다. 

primitive 자료형

int [] arr1 = new int[10];
IntStream stream1 = Arrays.stream(arr1);

double [] arr2 = new double[10];
DoubleStream stream2 = Arrays.stream(arr2);
  • Arrays의 static 메소드 stream을 통해 배열을 Stream으로 바꿀 수 있습니다. 

Wrapper Class 

Integer, String 같은 Wrapper Class 배열도 가능합니다.

Integer[] arr1 = new Integer[10];
Stream<Integer> stream2 = Arrays.stream(arr1);

String [] arr2 = new String[10];
Stream<String> stream3 = Arrays.stream(arr2);
  • primitive 자료형과 다르게 Stream <T>의 제네릭 형태로 반환되는 것을 볼 수 있습니다. 

Collection

List<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.stream();
  • Collection의 stream() 메소드를 통해서 Stream <T>을 생성할 수 있습니다. 

Stream.of(), ofNullalbe()

Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(1);
Stream<Integer> stream3 = Stream.ofNullable(1);
  • Stream.of()를 통해서 여러 개의 인자를 넣을 수 있고 단일 인자도 가능합니다.
  • Stream.of()의 경우 null을 넣을 수 없지만, ofNullable은 null을 넣을 수 있습니다.

Stream 중간 연산

데이터를 가공하는 중간 연산에 대해 알아보겠습니다.

Filter

filter(): 조건을 만족하는 데이터만 통과시킵니다.

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

 

Stream.filter()를 통해서 2로 나눠떨어지는 데이터만 통과합니다.

List<Integer> list = new ArrayList<>();
list.add(10);
list.add(19);
list.add(11);
list.add(12);

// 2로 나눠떨어 지는 것만 통과 가능합니다.
Stream<Integer> integerStream = list.stream().filter(a -> a % 2 == 0); // filter 중간 연산이므로 계속해서 반환형이 Stream 입니다. 

integerStream.forEach(System.out::println); // 최종 연산 forEach 통해 출력
                                            // 10 12 출력됩니다.
  • filter 중간 연산이므로 Stream을 반환형으로 하는 것을 알 수 있습니다.
  • forEach 최종 연산입니다. 가공된 데이터를 출력하는 역할입니다. 자세한 내용은 최종 연산에서 다루겠습니다.

map()

map은 데이터들을 1:1로 변환해주는 역할을 합니다. 이때 반환은 Stream에 감싸져 있습니다. 

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

List <String>을 map 이용해서 String의 길이로 반환하는 코드를 작성해보겠습니다. 

List<String> list = new ArrayList<>();
list.add("hi");
list.add("bye");
list.add("good");
list.add("school");
Stream<Integer> integerStream = list.stream().map(data -> data.length());
  • data를 map에 넘기고, 반환하는 것을 data의 길이로 받습니다.
  • String 하나에 길이 하나를 반환합니다. 따라서 1:1입니다. 
  • map이 끝난 후에는 자동으로 Stream에 담깁니다. 

flatMap()

map과 동일하게 데이터를 변환해주는 역할을 합니다. 하지만 1:1이 아닌 1:N으로 실행되고, 중간 연산이므로 마지막에 Stream으로 감싸줘야 합니다.

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

List <String>을 flatMap을 이용해서 데이터에 공백이 있다면 나눠서 Stream으로 만들겠습니다.

List<String> list = new ArrayList<>();

list.add("good");
list.add("s c h o o l");

Stream<String> stringStream = list.stream().flatMap(a -> Arrays.stream(a.split(" "))); 

stringStream.forEach(System.out::println); // 최종 연산으로 출력


// 출력물
// good
// s
// c
// h
// o
// o
// l
  • flatMap은 앞서 말했듯이 끝난 후 다시 Stream으로 감싸줘야 합니다. 
  • Arrays.stream()을 통해서 Stream을 만들고, 반환합니다.
  • map과 다르게 1:N으로 매핑되므로 school의 스펠링 하나하나를 가공한 것을 볼 수 있습니다.

Stream 최종 연산

중간 연산을 통해 만들어진 Stream을 최종 데이터의 형태로 뽑아내는 역할을 합니다.

sum, count, average, min, max

계산에 이용되는 메소드 입니다. 단순히 Stream 반환형이 아닌 IntStream, DoubleStream이어야 진행 가능합니다.

 

List <Integer>에 대한 총합, 개수, 평균, 가장 작은 수, 가장 큰 수를 구해보겠습니다.

IntStream intStream = IntStream.of(1, 2, 3, 5, 10);

int sum = intStream.sum();  // 총합 = 21

long count = intStream.count(); // 개수 = 5

OptionalInt optionalMax = intStream.max();
int max = optionalMax.getAsInt(); // 가장 큰 수 10

OptionalInt optionalMin = intStream.min();
int min = optionalMin.getAsInt(); // 가장 작은 수 1

OptionalDouble optionalAverage = intStream.average();
double average = optionalAverage.getAsDouble(); // 정수가 아닐 수 있으므로 double로 반환된다.

collect

Stream의 형태를 컬랙션 형태로 바꿀 수 있습니다.

<R, A> R collect(Collector<? super T, A, R> collector);

List <Integer>에 담긴 값들을 *10해 주고 다시 List에 담아보겠습니다.

List<Integer> list = new ArrayList<>();

ArrayList<Integer> collect = list.stream().map(a -> a * 10)
        .collect(
                () -> new ArrayList<>(),
                (c, s) -> c.add(s),
                (lst1, lst2) -> lst1.addAll(lst2)
        );
  • () -> new ArrayList<>() 담을 저장소 collection을 지정합니다.
  • (c, s) -> c.add(s) : c는 생성한 저장소를 의미합니다. s는 Stream에 있는 데이터입니다.
  • (lst1, lst2) -> lst1.addAll(lst2): 병렬 Stream에서 사용됩니다. 순차 Stream에서는 이용되지 않지만, null을 하면 오류가 발생합니다.

findAny, findFirst

finAny, findFirst 메서드는 Stream에 어떠한 값이 있다면 담아서 반환해줍니다. 없을 경우 null을 담아서 줍니다. 

 

두 메소드의 차이점

findFirst -> 첫 번째 요소 반환

findAny -> 아무 요소 반환 

Optional<T> findFirst();
Optional<T> findAny();
List<Integer> list = new ArrayList<>();

Optional<Integer> any = list.stream().findAny();
Optional<Integer> first = list.stream().findFirst();

System.out.println(any); // null 나타내는 Optional.empty 출력
System.out.println(any.get()); // null 이므로 NullPointerException 발생 
  • null을 안전하게 다룰 수 있습니다.

정리

지금까지 Stream에 대해서 알아봤습니다.

1. Stream을 배열, 컬렉션으로부터 생성하는 방법 

2. Stream 중간 연산을 이용하여 데이터를 가공하는 법

3. Stream 최종 연산을 통해서 데이터를 활용하는 법

 

 

'JAVA' 카테고리의 다른 글

JAVA Overloading & Overriding  (0) 2022.02.26
JAVA Enum  (0) 2022.02.24
JAVA Optional<T>  (0) 2022.02.23
JAVA 람다(lambda)  (0) 2022.02.21
JAVA Comparable And Comparator  (0) 2022.02.21