Java

stream 이란 ?

밍구밍구밍 2024. 8. 19. 20:46

스트림이란 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다. 여기서 데이터 소스를 추상화 하였다는 것은, 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재 사용성이 높아진다는 것을 의미 한다..

(스트림을 이용하면, 배열이나 Collection 뿐만 아니라 파일에 저장된 데이터도 모두 같은 방식으로 다룰 수 있다.)

 

0. 스트림의 표준 함수형 인터페이스 요약

  • Runnable : 매개변수가 없고 반환 값이 없을 때
  • Supplier<T> : 매개변수가 없고 반환값이 있을 때
  • Consumer<T> : 매개변수가 있고 반환값이 없을 때
  • Function<T, R> : 매개변수가 있고 반환값이 있을 때
  • Predicate<T> : 매개변수가 있고 반환 값이 boolean Type 일때

 

스트림 객체를 사용하는 방법을 알아보자

1. 문자열 배열 및 문자열 컬렉션(List) 를 스트림(stream) 을 사용하여 콘솔에 출력 하는 방법

public class StreamEx1 {

    public static void main(String[] args) {

        String[] strArr = {"aaa", "ccc", "bbb"};
        List<String> strList = Arrays.asList(strArr);
        // System.out.println("strList = " + strList); // strList = [aaa, ccc, bbb]

        // 위의 데이터 소스를 기반으로 하는 스트림 생성
        Stream<String> strStream1 = strList.stream();
        System.out.println("strStream1 = " + strStream1); // Array 스트림의 참조값
        Stream<String> strStream2 = Arrays.stream(strArr);
        System.out.println("strStream2 = " + strStream2); // List 스트림의 참조값

        /*strStream1.sorted().forEach(System.out::print);
        System.out.println();
        strStream2.sorted().forEach(System.out::print);*/

        strStream1.sorted().forEach(s -> System.out.print(s + " "));
        System.out.println();
        strStream2.sorted().forEach(s -> System.out.print(s + " "));
    }
}

- 문자열 배열 strArr 와 해당 문자열을 컬렉션 객체 strList 에 담았다.

이 두 데이터를 stream 으로 변환 하려면 먼저 Stream 객체를 제네릭 타입<String> 으로 선언하고 인스턴스를 생성한다.

( 배열 : strStream1, 컬렉션 : strStream2) 으로 선언

 

두 인스턴스를 먼저 sorted() 메서드를 통해 정렬 시키고, forEach 메서드를 사용하여 람다식으로 변환 후 출력한다.

참고로 위의 주석처리된 부분은 stream 객체의 공백 없이 람다식의 메서드 참조 형식을 사용하여 출력하는 방식이다.

 

※ 주의 : 스트림은 일회용이다.

스트림은 Iterator 처럼 일회용이다. Iterator 로 컬렉션의 요소를 모두 읽고 나면 다시 사용할 수 없는 것처럼, 스트림도 한번 사용하면 닫혀서 다시 사용할 수 없다. 필요하다면 스트림(Stream) 을 다시 생성해야 한다.

 

package study.stream;

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

public class StreamEx1 {

    public static void main(String[] args) {

        String[] strArr = {"aaa", "ccc", "bbb"};
        List<String> strList = Arrays.asList(strArr);
        // System.out.println("strList = " + strList); // strList = [aaa, ccc, bbb]

        // 위의 데이터 소스를 기반으로 하는 스트림 생성
        Stream<String> strStream1 = strList.stream();
        System.out.println("strStream1 = " + strStream1); // Array 스트림의 참조값
        Stream<String> strStream2 = Arrays.stream(strArr);
        System.out.println("strStream2 = " + strStream2); // List 스트림의 참조값

        strStream1.sorted().forEach(System.out::print);
        System.out.println();
        strStream2.sorted().forEach(System.out::print);

        strStream1.sorted().forEach(s -> System.out.print(s + " "));
        System.out.println();
        strStream2.sorted().forEach(s -> System.out.print(s + " "));
    }
}

(위의 코드와 같이 스트림을 재사용하게 되면 아래와 같이 JVM 이 Runtime 에러를 띄운다.)

 

2. 스트림은 작업을 내부 반복으로 처리한다.

스트림을 이용한 작업이 간결한 이유는 '내부 반복' 이다. 내부 반복이라는 것은 반복문을 메서드의 내부에 숨길 수 있다는 것을 의미한다.

(아래는 forEach() 문을 메서드 내부로 넣은 예시이다.)

public class StreamEx2 {

    public static void main(String[] args) {

        String[] strArr = {"aaa", "bbb", "ccc"};

        // 배열 내부의 데이터를 for 문을 사용하여 출력
        for (String string : strArr) {
            System.out.print(string + " ");
        }

        System.out.println();

        // 스트림을 사용한 배열의 데이터를 Collection 변환 + 정렬 후 출력
        Stream<String> stream = Arrays.stream(strArr).sorted();
        stream.forEach(System.out::print);
    }
}

 

3. Stream 의 중간 연산 메서드 (forEach() 메서드가 수행되기 전까지의 메서드는 중간 연산)

- 지연된 연산 : 스트림 연산에서 중간 연산 메서드를 추가한다고 해서 중간연산이 되는 것이 아니다

중간연산 메서드 선언 후 최종 연산 메서드를 호출해야 중간에 선언한 메서드가 실행되는 것이다.

  • distinct() : 중복 제거
  • filter() : 조건에 안 맞는 요소 제외
  • skip() : 스트림의 일부를 잘라낸다
  • peek() : 스트림의 일부를 건너뛴다
  • sorted() : 스트림 요소를 정렬
public class StreamEx3 {

    public static void main(String[] args) {

        String[] strArr = {"dd", "aaa", "CC", "ccc", "b", "b", "aaa"};

        // Stream.of() : 문자열 배열 strArr 를 Stream 객체로 변환하는 메서드
        Stream<String> stream = Stream.of(strArr); // 문자열이 배열이 소스인 스트림
//        stream.sorted().forEach(System.out::print);

        // filter() :  stream 배열에서 조건을 수행하여 조건에 만족하는 값만 출력하는 코드
        Stream<String> filterdStream = stream.filter(s -> s.length() > 1);
        filterdStream.forEach(System.out::print);

        System.out.println();

        // distinct() : 주어진 데이터의 중복을 제거 하는 메서드
        /* filter() 메서드를 수행하면서 생성된 stream 객체를 한번 사용 했기 때문에
        newStream1 라는 스트림 객체를 다시 생성 했다.*/
        Stream<String> newStream1 = Stream.of(strArr);
        Stream<String> distinctedStream = newStream1.distinct();
        distinctedStream.forEach(System.out::print);

        System.out.println();

        // sorted() : 스트림 객체를 정렬
        Stream<String> newStream2 = Stream.of(strArr);
        Stream<String> sortededStream = newStream2.sorted();
        sortededStream.forEach(System.out::print);

        System.out.println();

        // limit() : 배열의 요소중 '0부터 n-1' 까지 출력하는 메서드
        // ex) limit(4) 를 입력하면 0부터 3까지의 인덱스의 value 3개를 출력한다.
        Stream<String> newStream3 = Stream.of(strArr);
        Stream<String> limitedStream = newStream3.limit(3);
        limitedStream.forEach(System.out::print);

        System.out.println();
        
        // count() : 요소의 개수 세기
        Stream<String> newStream4 = Stream.of(strArr);
        long total = newStream4.count();
        System.out.println("total = " + total);
    }
}

 

 

4. Stream 의 최종 연산 메서드 (forEach() 등이 수행 후 Stream 은 삭제되며 다시 생성 해야 한다)

  • forEach() : 각 요소에 지정된 작업 수행
  • count() : 스트림 요소의 개수 반환
  • Optional<T> max() : 스트림의 최대 값을 반환
  • Optional<T> min() : 스트림의 최소 값을 반환
  • Optional<T> reduce() : 스트림의 요소를 하나씩 줄여 가면서 계산한다.
  • R collect() : 스트림의 요소를 수집한다. >> 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용

 

1) .of : 배열 및 Collection 객체를 Stream 객체로 변환하는 메서드 

// Collection 객체를 Stream 으로 변환
Stream<List<String>> list1 = Stream.of(list);

 

2) filter() : 스트림 내부에 조건을 수행하여 해당 조건을 만족하는 데이터를 출력하기위한 메서드

 

3) distinct() : 데이터의 중복을 제거

 

4) sorted() : 내부의 데이터를 정렬 (Arrays.sort() 또는 Collections.sort() 와 같은 개념)

 

5) limit() : 스트림 배열 및 Collection 내부의 요소중 0부터 n-1 까지 출력하는 메서드

ex ) limit() 메서드 동작 방식

limit(n) 은 스트림에서 최초의 'n' 개의 요소만을 반환한다. 여기서 n 은 포함할 요소의 수이다.

n 이 스트림의 전체 요소 개수보다 크면, 스트림의 모든 요소를 반환한다.

 

6) count() : 스트림 배열 및 Collection 내부의 인덱스 개수를 반환