티스토리 뷰

Java

[Java] Stream이란? with 공식문서

wlsdl00 2023. 11. 5. 01:38

1. Stream 배경

  • Stream이 처음 언급된 곳은 1965년 Peter Landin의 논문 "Correspondence between ALGOL 60 and Church's Lambda-notation"이다.
  • 위 논문에서는 함수형 프로그래밍과 함께 Stream도 다루고 있다.
  • Java 8부터 Stream이 java에 도입되었다.
  • Stream의 주 목적은 복잡한 데이터 처리 간소화, 코드 가독성 향상, 병렬 처리의 쉬운 적용이다.
  • 함수형 프로그래밍에서의 stream과 Java에서의 stream은 페러다임이 약간 다르다.

    → 함수형 프로그래밍에서의 Stream은 순수성과 지연 평가에 초점을 두었지만 java Stream은 다양한 데이터 소스에 대한 지원과 병렬 처리에 좀 더 초점이 맞춰졌다.


2. Stream 공식 문서

java.util.stream (Java Platform SE 8 )
See: Description
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
💡
A sequence of elements supporting sequential and parallel aggregate operations.
  • 데이터의 시퀀스들에 대해 순차적, 혹은 병렬적인 연산을 쉽게 수행할 수 있도록 제공하는 API
  • Sequence : 일련의 순서를 갖는 항목들의 집합, 특정 순서대로 나열된 데이터들의 집합, 데이터의 연속체

    ex) 배열의 원소, 컬렉션의 원소 등등

💡
No storage. A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
  • Steam은 데이터 구조가 아니라 데이터 흐름을 나타낸다.
  • 즉, Stream은 데이터를 저장하는 목적이 아닌 연산을 수행하는 통로, 혹은 파이프라인의 역할을 한다.

💡
Functional in nature. An operation on a stream produces a result, but does not modify its source. For example, filtering a Stream obtained from a collection produces a new Stream without the filtered elements, rather than removing elements from the source collection.
  • 함수적 특성을 가지고 있다.
  • Stream 연산은 결과를 생성하지만 그 소스, 원본을 수정하지 않는다.
  • 예를 들어 컬렉션의 Stream을 필터링하면 원본 컬렉션은 유지되고 필터링된 결과만을 담은 새로운 스트림이 생성된다.

💡
Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, "find the first String with three consecutive vowels" need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.
  • Stream은 지연 연산을 추구한다, 즉 실제로 필요한 시점까지 연산을 미룬다.
  • 스트림은 중간 연산최종 연산으로 나누어져있다.
  • 중간 연산에서는 실제 처리가 이루어지지 않고 최종 연산에서만 실제 처리가 이루어진다.
  • 즉, 중간 연산은 Stream을 반환하고 최종 연산은 Stream을 소비한다.

💡
Possibly unbounded. While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.
  • Stream은 유한한 크기를 가지는 컬렉션과 달리 꼭 유한한 크기를 가지지 않는다.
  • 특정 연산을 사용하면 무한 스트림에서도 유한한 시간에 연산을 할 수 있다.

💡
Consumable. The elements of a stream are only visited once during the life of a stream. Like an Iterator, a new stream must be generated to revisit the same elements of the source.
  • Stream의 요소는 Stream의 수명동안 한번만 사용됨.
  • 즉, Stream은 소비형이며 한번 처리된 요소를 다시 처리하려면 새로운 Stream을 생성해야 한다.

💡
Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.
  • Stream 연산은 중간 연산종단 연산으로 나누어지고, 이들이 결합되서 Stream 파이프라인을 형성한다.
  • 중간 연산은 .filter , .map 등으로 구성된다.
  • 종단 연산은 .forEach , .reduce 등이 있다.

💡
Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.
  • 중간 연산은 새로운 Stream을 반환한다.
  • 중간 연산은 지연 연산을 수행하며, 종단 연산의 실행전까지 시작되지 않는다.

💡
Terminal operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.
  • 종단 연산은 Stream을 순회한 후에 결과나 Side-effect를 생성한다.
  • 종단 연산이 수행된 후에는 Stream은 “소비된 것”으로 간주되며 다시 사용할 수 없다.

💡
Processing streams lazily allows for significant efficiencies; in a pipeline such as the filter-map-sum example above, filtering, mapping, and summing can be fused into a single pass on the data, with minimal intermediate state. Laziness also allows avoiding examining all the data when it is not necessary; for operations such as "find the first string longer than 1000 characters", it is only necessary to examine just enough strings to find one that has the desired characteristics without examining all of the strings available from the source. (This behavior becomes even more important when the input stream is infinite and not merely large.)
int sum = widgets.stream()
                      .filter(b -> b.getColor() == RED)
                      .mapToInt(b -> b.getWeight())
                      .sum();
  • 위와 같은 filter - map - sum 예시에서 이 3개의 연산이 sigle pass, 즉 데이터를 한번만 순회하면서 필요한 모든 연산을 완료하게 된다.
  • 이러한 방식을 사용할 경우 중간 상태를 최소화하고 연산의 효율성을 높인다.

💡
Intermediate operations are further divided into stateless and stateful operations. Stateless operations, such as filter and map, retain no state from previously seen element when processing a new element -- each element can be processed independently of operations on other elements. Stateful operations, such as distinct and sorted, may incorporate state from previously seen elements when processing new elements.
  • 중간 연산은 무상태 연산상태 연산 두 가지로 나누어진다.
  • filter 는 각 요소가 특정 조건을 만족하는지 확인하고 map 연산은 각 요소를 다른 형태로 변환한다. 이러한 경우들은 이전 요소의 정보를 필요로 하지 않으므로 무상태 연산이다.
  • distinctsorted 연산의 경우 정렬을 하거나 중복을 제거하기위해 이전에 처리된 요소의 정보를 기억하고 있어야 한다. 이러한 연산은 상태 연산이다.

💡
Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream. As a result, under parallel computation, some pipelines containing stateful intermediate operations may require multiple passes on the data or may need to buffer significant data. Pipelines containing exclusively stateless intermediate operations can be processed in a single pass, whether sequential or parallel, with minimal data buffering.
  • 상태 연산은 전체 데이터를 처리한 후에 결과를 내야하기 때문에 병렬처리에서 주의해야 한다. 예를 들어 sorted 의 경우는 모든 요소를 순회할 때까지 정렬 결과를 생성할 수 없다.
  • 상태연산은 병렬 처리 시 데이터를 여러번 순회해야 할 수도 있기 때문에 리소스가 많이 소모되고 효율성도 저하될 수 있다.
  • 따라서 상태 연산을 사용할 때는 주의해야하며 필요한 경우한 사용하는 것이 좋다.

💡
Further, some operations are deemed short-circuiting operations. An intermediate operation is short-circuiting if, when presented with infinite input, it may produce a finite stream as a result. A terminal operation is short-circuiting if, when presented with infinite input, it may terminate in finite time. Having a short-circuiting operation in the pipeline is a necessary, but not sufficient, condition for the processing of an infinite stream to terminate normally in finite time.
  • 일부 연산은 단축 연산으로 간주된다.
  • 단축 연산이란 두 가지 경우에서의 연산을 의미한다.
    • 무한한 입력 Stream에 대해서도 limit와 같은 중간 연산을 통해 유한한 스트림으로 결과를 생성할 수 있다.
    • 무한한 입력 Stream에 대해서도 anyMatch와 같은 종단 연산이 유한한 시간 내에 종료될 수 있다.
  • 주의해야 할 점은 단축 연산은 Stream을 유한한 시간 내에 처리하게 해주기 위한 필요 조건이지만, 단축 연산만으로는 Stream을 유한한 시간 내에 처리해주는 충분한 조건이 아니다. → 따라서 연산의 복잡성, 데이터의 특성, 그리고 파이프라인의 다른 연산들도 고려해야 한다.


2. 공식문서 요약

기본 개념

  • Java Stream은 데이터 처리 연산을 지원하는 기능적인 스타일의 클래스와 인터페이스를 제공한다.
  • Stream은 데이터 구조가 아니라, 데이터의 흐름을 나타낸다.

연산 유형

Steam은 중간 연산종단 연산 두 가지 유형의 연산을 지원한다.

  • 중간 연산다른 Steam을 반환한다.
  • 종단 연산스트림을 소비한다.

함수형 프로그래밍

Steam은 함수형 프로그래밍 스타일을 적용하여 데이터 처리를 단순화한다.

상태와 무상태 연산

  • 중간 연산은 상태를 가질 수도 있고 가지지 않을 수도 있다.
  • 상태 연산은 전체 입력을 처리하기 전에 결과를 생성할 수 없다.

단축 연산

  • 일부 연산은 무한 스트림에 대해서도 유한한 시간 내에 결과를 생성할 수 있다.
  • 단축 연산은 유한한 시간 내에 연산하기 위해 필요하지만 충분한 조건은 아니다.

병렬 처리

  • 스트림은 병렬 처리를 쉽게 할 수 있도록 설계되어 있다. 병렬 스트림을 사용하면 멀티 코어 아키텍처의 이점을 취할 수 있다.
  • 여기서 다루진 않았지만 stream 말고 parallelStream 이라는 키워드로 병렬처리를 지원한다.


3. Stream 연산의 종류

중간 연산 (Intermediate Operations)

연산 이름설명예시
filter조건에 맞는 요소만 선택stream.filter(x -> x > 2)
map각 요소를 변환stream.map(x -> x * 2)
flatmap각 요소를 여러 요소로 변환stream.flatMap(x -> Stream.of(x, x+1))
distinct중복 요소 제거stream.distinct()
sorted요소 정렬stream.sorted()
peek요소를 소비하지 않고 다른 작업 수행stream.peek(x -> System.out.println(x))
limit스트림 크기 제한stream.limit(3)
skip처음 n개 요소 생략stream.skip(2)

종단 연산 (Terminal Operations)

연산 이름설명예시
forEach각 요소에 작업 수행stream.forEach(x -> System.out.println(x))
toArray스트림을 배열로 변환stream.toArray()
reduce스트림을 하나의 요소로 축소stream.reduce((x, y) -> x + y)
collect스트림을 컬렉션으로 변환stream.collect(Collectors.toList())
min최소값 찾기stream.min(Comparator.comparingInt(x -> x))
max최대값 찾기stream.max(Comparator.comparingInt(x -> x))
count요소 개수 세기stream.count()
anyMatch하나라도 조건에 맞으면 truestream.anyMatch(x -> x > 2)
allMatch모든 요소가 조건에 맞으면 truestream.allMatch(x -> x > 2)
noneMatch모든 요소가 조건에 안 맞으면 truestream.noneMatch(x -> x > 2)
findFirst첫 번째 요소 찾기stream.findFirst()
findAny임의의 요소 찾기 (병렬 스트림에서 유용)stream.findAny()


4. Stream 사용 예시

1. 리스트의 모든 요소 출력

List<String> list = Arrays.asList("a", "b", "c");
list.stream()
    .forEach(System.out::println);

2. 리스트에서 짝수만 필터링

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .collect(Collectors.toList());

3. 리스트의 모든 요소를 제곱

List<Integer> squared = numbers.stream()
                               .map(n -> n * n)
                               .collect(Collectors.toList());

4. 리스트의 최소값 찾기

Optional<Integer> min = numbers.stream()
                               .min(Comparator.naturalOrder());

5. 리스트의 최대값 찾기

Optional<Integer> max = numbers.stream()
                               .max(Comparator.naturalOrder());

6. 리스트의 요소 합계

int sum = numbers.stream()
                 .reduce(0, Integer::sum);

7. 문자열 리스트를 쉼표로 연결

String joined = list.stream()
                    .collect(Collectors.joining(", "));

8. 리스트에서 중복 제거

List<Integer> distinct = numbers.stream()
                                .distinct()
                                .collect(Collectors.toList());

9. 무한 스트림에서 처음 10개의 짝수 찾기

List<Integer> first10Even = Stream.iterate(0, n -> n + 1)
                                  .filter(n -> n % 2 == 0)
                                  .limit(10)
                                  .collect(Collectors.toList());

10. 병렬 스트림 사용하기

int parallelSum = numbers.parallelStream()
                         .reduce(0, Integer::sum);


참고. JAVA의 버전 표기법

Java Platform, Standard Edition 8 Names and Versions
This document explains the names and version numbers that are associated with Java Platform, Standard Edition 8.
https://www.oracle.com/java/technologies/javase/jdk8-naming.html
  • 7, 8, 9와 같은 숫자는 java version number를 의미한다.
  • 1.8.0, 1.9.0의 의미는 java version String이다.
  • 즉, JAVA 8 == JAVA SE8 == JAVA 1.8.0 → true
Java version history
The Java language has undergone several changes since JDK&#160;1.0 as well as numerous additions of classes and packages to the standard library. Since J2SE&#160;1.4, the evolution of the Java language has been governed by the Java Community Process (JCP), which uses Java Specification Requests (JSRs) to propose and specify additions and changes to the Java platform. The language is specified by the Java Language Specification (JLS); changes to the JLS are managed under JSR&#160;901. In September 2017, Mark Reinhold, chief Architect of the Java Platform, proposed to change the release train to "one feature release every six months" rather than the then-current two-year schedule.&#91;1&#93;&#91;2&#93; This proposal took effect for all following versions, and is still the current release schedule.
https://en.wikipedia.org/wiki/Java_version_history
  • 참고로 4버전에서 5버전으로 넘어갈 때부터 버전 넘버만을 사용하고 있다.

Uploaded by N2T

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
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
글 보관함