코린이의 소소한 공부노트

스트림의 정의와 특징 본문

Java

스트림의 정의와 특징

무지맘 2023. 1. 30. 23:53

[스트림의 정의]

1. 스트림은 다양한 데이터 소스(컬렉션, 배열)를 표준화된 방법으로 다루기 위한 것이다.

- 컬렉션 프레임워크의 경우, ListSet을 다루는 방법과 Map을 다루는 방법이 달라 완전히 표준화되었다고 말하긴 어렵다.

- 하지만 데이터 소스를 스트림으로 만들고 나면, 다루는 방법은 똑같다.

2. 스트림은 데이터의 연속적인 흐름을 말한다.

- 중간 연산을 0~n번 거친 후 최종 연산 0~1번을 거쳐 결과가 나온다.

// Stream<T> Collection.stream()을 이용해 스트림으로 변환(생성)
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream(); // 컬렉션을 스트림으로

Stream<String> strStream = Stream.of(new String[] {"a","b","c" }); // 배열을 스트림으로

Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0,2,4,..가 들어있는 스트림

Stream<Double> randomStream = Stream.generate(Math::random); // 람다식을 이용한 생성

IntStream intStream = new Random().ints(5); // 난수 스트림(크기가 5)

 

[스트림이 제공하는 기능]

1. 중간 연산: 연산결과가 스트림인 연산. 반복적으로 적용가능

2. 최종 연산: 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 한 번만 적용가능

stream.distinct().limit(5).sorted().forEach(System.out::println)
//     중복제거 / 5개 자르기 / 정렬 // 출력
//              중간 연산         // 최종 연산

String[] strArr = { "dd","aaa","CC","cc","b" };
Stream<String> stream = Stream.of(strArr); // 문자열 배열이 소스인 스트림
Stream<String> filteredStream = stream.filter(s->s.length()%2==0); // 걸러내기(중간 연산)
Stream<String> distinctedStream = filteredStream.distinct(); // 중복제거(중간 연산)
Stream<String> sortedStream = distinctedStream.sorted(); // 정렬(중간 연산)
Stream<String> limitedStream = sortedStream.limit(5); // 스트림 자르기(중간 연산)
long total = limitedStream.count(); // 요소 개수 세기(최종연산) <- 실행해보기
System.out.println(total); // 3. 문자열의 길이가 짝수인 것은 3개뿐이다.

 

[스트림의 특징]

1. 스트림은 데이터 소스(원본)로부터 데이터를 읽기만 할 뿐 변경하지 않는다.(read-only)

List<Integer> list = Arrays.asList(3,1,5,4,2);
List<Integer> sortedList = list.stream().sorted() // list를 정렬해서
                           .collect(Collectors.toList()); // 새로운 List에 저장
System.out.println(list); // [3, 1, 5, 4, 2]
System.out.println(sortedList); // [1, 2, 3, 4, 5]

2. 스트림은 Iterator처럼 일회용이다. 필요하면 다시 생성해야 한다.

strStream.forEach(System.out::println); // 모든 요소를 화면에 출력(최종연산)
int numOfStr = strStream.count(); // 에러. 스트림이 이미 닫혔음
// java.lang.IllegalStateException: stream has already been operated upon or closed

3. 최종 연산 전까지 중간 연산이 수행되지 않는다.(지연된 연산)

IntStream intStream = new Random().ints(1,46); // 1~45범위의 무한 스트림
intStream.distinct().limit(6).sorted() // 중간 연산
         .forEach(i->System.out.print(i+",")); // 최종 연산
// 무한 스트림이라 이런 코드가 불가능할 것 같지만, 가능한 코드다.
// 최종 연산 전까지 중간 연산들을 체크해놨다가 최종 연산 단계에서 체크해둔 중간 연산들을
// 실행하기 때문에 이런 코드가 가능한 것이다.

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

for(String str : strList) // 배열의 요소를 하나씩 꺼내서 출력하는 for문
    System.out.println(str);
    
// 위의 for문을 스트림을 이용한 코드로 바꾸면
Stream.of(strList).forEach(System.out::println);

// 스트림의 forEach() 코드
void forEach(consumer<? super T> action){
    Objects.requireNonNull(action); // 매개변수의 널 체크
    for(T t : src) // 내부 반복
        action.accept(T);
}

5. 스트림의 작업을 병렬로 처리한다.(병렬스트림 - 멀티쓰레드)

Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");
int sum = strStream.parallel() // 병렬 스트림으로 전환(속성만 변경)
                   .mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합
// parallel()의 반대는 sequential()

6. 기본형 스트림: IntStream, LongStream, DoubleStream

- 데이터 소스가 기본형일 때 사용한다.

- int는 스트림으로 변환하면 Integer로 오토박싱된다. 그래서 스트림을 사용할 때 int로 언박싱을 해야 한다.

- Stream<Integer>대신 IntStream사용함으로써 오토박싱/언박싱의 비효율이 제거된다.

- 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공한다. T가 어떤 타입인지 모르기 때문에 Stream<T>에서는 숫자와 관련된 메서드가 별로 없다.

'Java' 카테고리의 다른 글

스트림의 중간 연산  (0) 2023.02.07
스트림 생성하기  (0) 2023.02.01
람다식과 메서드 참조  (0) 2023.01.13
function 패키지와 메서드  (0) 2023.01.12
함수형 인터페이스의 정의와 활용  (0) 2022.12.29