소소한개발팁
article thumbnail
Published 2023. 7. 6. 13:18
Stream 컴퓨터 언어/Java
반응형

 

Stream

연속된 요소 시퀀스: 스트림은 연속된 요소들의 시퀀스로 이루어져 있습니다. 이를 통해 스트림을 이용하여 데이터의 연산과 처리를 연속적으로 수행할 수 있습니다.

내부 반복: 스트림은 내부 반복(Internal iteration)을 사용하여 요소들을 처리합니다. 내부 반복은 스트림 자체에서 요소들을 처리하는 방식으로, 개발자는 명시적인 반복문을 작성할 필요 없이 스트림의 메서드를 사용하여 요소들을 처리할 수 있습니다.

게으른 실행: 스트림은 게으른 실행(Lazy evaluation)을 지원합니다. 이는 스트림의 연산이 실제로 필요한 시점까지 실행되지 않음을 의미합니다. 즉, 스트림은 연산을 지연시키고 필요한 요소만 처리함으로써 효율적인 처리를 가능하게 합니다.

파이프라인 연산: 스트림은 파이프라인 연산을 지원합니다. 파이프라인은 여러 스트림 연산들을 연결하여 데이터를 처리하는 방식을 말합니다. 이를 통해 개발자는 데이터 처리 과정을 선언적으로 표현할 수 있으며, 중간 연산과 최종 연산으로 파이프라인을 구성할 수 있습니다.

재사용 가능: 스트림은 일회성이 아니며, 재사용이 가능합니다. 한 번 생성된 스트림은 필요에 따라 여러 번 사용할 수 있습니다.

 

출처 :https://steady-coding.tistory.com/309

 

 물고기와 같은 어류의 이동을 stream이라고 정의할 수 있습니다.

먼저, 어부가 어류 중에서도 고등어를 잡고 싶어서 그물로 고등어를 잡았습니다. 이 행위를 filter라고 하고, 이 연산자를 중간 연산자라고 합니다.

그리고 고등어를 포장하지 않고 생으로 팔 수는 없기 때문에 상자에 담아야 합니다. 이 행위를 map이라고 하고, 이 연산자도 마찬가지로 중간 연산자라고 합니다.

마지막으로, 고등어가 실린 수많은 상자를 운반하여 다른 곳으로 이동하면서 끝이 납니다. 이 행위를 collect라고 하고, 이 연선자는 최종 연산자라고 합니다.

스트림은 수많은 데이터의 흐름 속에서 각각의 원하는 값을 가공하여 최종 소비자에게 제공하는 역할을 한다고 보면 되겠습니다.

출처 : https://steady-coding.tistory.com/309

 

 

 

컬렉션에서 스트림을 생성하는 방법

  • Arrays.stream() 메서드를 사용하여 Arrays 에서 스트림을 생성합니다.
  • List.stream() 메서드를 사용하여 List 에서 스트림을 생성합니다.
  • Set.stream() 메서드를 사용하여 Set 에서 스트림을 생성합니다.
  • Map.values().stream() 메서드를 사용하여 Map 의 값에서 스트림을 생성합니다.
  • Map.keySet().stream() 메서드를 사용하여 Map 의 키에서 스트림을 생성합니다.

 

//Arrays.stream()
int[] arr = {1, 2, 3, 4, 5};
Stream<Integer> stream = Arrays.stream(arr);

//List.stream() 
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream();

//Set.stream()
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Stream<Integer> stream = set.stream();

//Map.values().stream()
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Stream<Integer> stream = map.values().stream();

//Map.keySet().stream() 
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Stream<String> stream = map.keySet().stream();

 

 

스트림에서 연산수행하는 방법

스트림을 생성한 후에는 스트림에 대해 다양한 연산을 수행할 수 있습니다. 스트림에 대해 수행할 수 있는 연산에는 필터링, 매핑, 정렬, 그룹화, 집계 등이 있습니다. 스트림에 대한 연산을 모두 수행한 후에는 스트림을 사용하여 컬렉션을 생성할 수 있습니다. 스트림을 사용하여 컬렉션을 생성하는 방법은 collect() 메서드를 사용하는 것입니다. collect() 메서드는 스트림의 요소를 사용하여 새로운 컬렉션을 생성합니다.

 

사용빈도가 높은 중간연산

Filter

Stream<T> filter(Predicate<? super T> predicate)
  • T: 스트림의 요소 유형을 나타냅니다.
  • Predicate<? super T>: 스트림의 요소를 평가하는 조건을 나타내는 Predicate입니다.

 

filter() 메서드는 중간 연산으로, 다른 중간 연산과 함께 체인으로 연결하여 사용할 수 있습니다. 또한 단일 파라미터를 가지며, 해당 파라미터는 Predicate 인터페이스의 구현체여야 합니다.

Predicate 인터페이스는 함수형 인터페이스로, 주어진 값에 대한 조건을 평가하는 test() 메서드를 가지고 있습니다. test() 메서드는 파라미터로 받은 값을 평가하여 true 또는 false를 반환합니다. filter() 메서드는 Predicate의 test() 메서드를 호출하여 조건을 확인하고, true를 반환하는 요소만 유지합니다.

 

Predicate 구현체는 람다 표현식이나 메서드 참조를 사용하여 정의할 수 있습니다. 파라미터로 전달된 Predicate의 test() 메서드는 스트림의 각 요소에 대해 호출되며, test() 메서드가 true를 반환하는 경우에만 해당 요소가 결과 스트림에 포함됩니다.

 

//리스트에서 특정 조건을 만족하는 숫자를 찾는 경우
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .collect(Collectors.toList());

 

 

Distinct

 중복된 요소를 제거하고, 유일한 요소로 이루어진 새로운 스트림을 반환합니다.

//숫자 리스트에서 중복을 제거하는 경우
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());

 

 

Map

<R> Stream<R> map(Function<? super T, ? extends R> mapper)
  • T: 스트림의 요소 유형을 나타냅니다.
  • R: 매핑된 요소의 유형을 나타냅니다.
  • Function<? super T, ? extends R>: 요소를 매핑하는 함수를 나타내는 Function입니다.
  • Function은 함수형 인터페이스로, apply() 메서드를 가지며 주어진 입력 값을 받아서 변환된 결과를 반환합니다.

 

map() 메서드는 단일 파라미터를 가지며, 해당 파라미터는 함수형 인터페이스인 Function의 구현체여야 합니다.

Function 인터페이스는 단일 입력 값을 받아서 다른 타입의 결과를 반환하는 apply() 메서드를 가지고 있습니다. map() 메서드는 스트림의 각 요소에 대해 전달된 Function의 apply() 메서드를 호출하고, 반환된 결과로 새로운 스트림을 생성합니다.

map() 메서드의 파라미터로 전달되는 Function 구현체는 람다 표현식이나 메서드 참조를 사용하여 정의할 수 있습니다. 파라미터로 전달된 Function은 스트림의 각 요소에 대해 호출되며, 반환된 결과로 새로운 스트림을 생성합니다.

 

List<String> strings = Arrays.asList("apple", "banana", "orange");
List<Integer> lengths = strings.stream()
                              .map(String::length)
                              .collect(Collectors.toList());

 

 

FlatMap

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
  • R: 매핑 함수를 적용한 후 생성되는 평면화된 스트림의 요소 유형을 나타냅니다.
  • mapper: 요소에 적용할 매핑 함수로, 각 요소를 변환하여 하위 스트림(Stream)으로 반환합니다.

flatMap() 메서드는 스트림의 각 요소에 대해 mapper 함수를 적용하고, 그 결과로 생성된 각 하위 스트림을 평면화하여 하나의 스트림으로 만듭니다. 이렇게 생성된 스트림은 평면화된 스트림이며, R 유형의 요소를 가지게 됩니다.

 

//리스트의 리스트를 평면화하는 경우
List<List<Integer>> nestedList = Arrays.asList(
    Arrays.asList(1, 2),
    Arrays.asList(3, 4),
    Arrays.asList(5, 6)
);

List<Integer> flattenedList = nestedList.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());

 

 

 Limit

Stream<T> limit(long maxSize)
  • maxSize: 유지할 요소의 최대 개수를 나타내는 long 값입니다.

limit() 메서드는 스트림에서 처음부터 maxSize 이하의 요소를 유지하고, 그 이후의 요소는 버립니다. 따라서 결과 스트림은 최대 maxSize 개의 요소로 구성됩니다.

 

//숫자 리스트에서 처음 3개의 요소를 가져오는 경우
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
                                      .limit(3)
                                      .collect(Collectors.toList());

 

 

Skip

Stream<T> skip(long n)
  • n: 건너뛸 요소의 개수를 나타내는 long 값입니다.

skip() 메서드는 스트림에서 처음부터 n 개의 요소를 건너뛰고, 그 이후의 요소를 유지합니다. 따라서 결과 스트림은 처음 n 개의 요소를 제외한 나머지 요소로 구성됩니다.

 

//숫자 리스트에서 처음 2개의 요소를 건너뛰고 나머지 요소들을 가져오는 경우
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skippedNumbers = numbers.stream()
                                      .skip(2)
                                      .collect(Collectors.toList());

 

 

Sorted

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
  • T: 스트림의 요소 유형을 나타냅니다.
  • Comparator<? super T>: 요소를 비교하는 데 사용되는 Comparator입니다. 
  • Comparator는 함수형 인터페이스로, compare() 메서드를 가지며 두 개의 값을 비교하여 정렬 순서를 결정합니다.

 

 sorted() 메서드는 파라미터 없이 호출할 수도 있으며, 기본적으로 요소를 자연적인 순서로 정렬합니다. 또는 Comparator를 파라미터로 전달하여 사용자 정의 순서로 요소를 정렬할 수도 있습니다.

 

//숫자 리스트를 오름차순으로 정렬하는 경우
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 3);
List<Integer> sortedNumbers = numbers.stream()
                                     .sorted()
                                     .collect(Collectors.toList());

 

//문자열의 길이에 따라 내림차순으로 정렬하는 경우
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<String> sortedNames = names.stream()
                               .sorted(Comparator.comparingInt(String::length).reversed())
                               .collect(Collectors.toList());

 

 

Peek

Stream<T> peek(Consumer<? super T> action)
  • action: 요소를 소비하고 중간 처리를 수행하는 Consumer입니다. Consumer는 함수형 인터페이스로, accept() 메서드를 호출하여 요소를 소비합니다.
    peek() 메서드는 스트림의 각 요소를 소비하면서 주어진 action을 수행합니다. action은 요소를 소비하는 동작을 정의하는 Consumer 객체입니다. Consumer의 accept() 메서드는 요소를 입력으로 받아 처리하는 동작을 수행합니다.

peek() 메서드는 스트림의 중간 연산으로 사용되며, 요소를 변경하지 않고 중간 처리를 수행합니다. 주로 디버깅, 로깅, 요소 확인 등의 목적으로 사용됩니다. 중간 처리 작업이 수행되는 동안 스트림은 그대로 유지되므로, peek() 메서드는 스트림 파이프라인에서 다른 중간 연산과 함께 사용될 수 있습니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> incrementedNumbers = numbers.stream()
                                          .peek(n -> System.out.println("Before increment: " + n))
                                          .map(n -> n + 1)
                                          .peek(n -> System.out.println("After increment: " + n))
                                          .collect(Collectors.toList());

 

 

 

 

사용빈도가 높은 최종연산

스트림의 최종 연산은 스트림 파이프라인에서 최종적인 결과를 생성하거나 스트림의 요소를 소비하는 데 사용되는 메서드입니다. 최종 연산은 스트림의 요소를 처리하거나 결과를 생성하기 때문에 스트림 파이프라인에서 단 한 번만 호출될 수 있습니다. 최종 연산은 다음과 같은 구조를 가지고 있습니다

<R> R someTerminalOperation();
  • R: 최종 연산의 반환값의 유형을 나타냅니다.

 

 

ForEach

void forEach(Consumer<? super T> action)
  • action: 요소를 소비하고 수행할 동작을 정의하는 Consumer입니다. Consumer는 함수형 인터페이스로, accept() 메서드를 호출하여 요소를 소비합니다.

forEach() 메서드는 스트림의 각 요소를 소비하면서 action으로 정의된 동작을 수행합니다. action은 요소를 입력으로 받아 처리하는 Consumer 객체입니다. Consumer의 accept() 메서드는 요소를 입력으로 받아 처리하는 동작을 수행합니다.

forEach() 메서드는 스트림의 최종 연산으로 사용되며, 스트림의 각 요소를 순차적으로 처리합니다. 스트림의 각 요소에 대해 동작을 수행하며, 주로 요소를 출력하거나 다른 작업을 수행할 때 사용됩니다.

 

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

names.stream()
     .forEach(name -> System.out.println("Hello, " + name + "!"));

 

 주의할 점은 forEach() 메서드는 스트림의 요소를 순차적으로 처리하기 때문에 병렬 처리가 필요한 경우에는 다른 최종 연산을 고려해야 합니다.

 

 

Reduce

Optional<T> reduce(BinaryOperator<T> accumulator)
  • 반환값으로 Optional을 사용하는 이유는, 스트림이 비어있을 수도 있기 때문에 결과가 없을 수 있기 때문입니다.
  • T: 요소의 유형을 나타냅니다.
  • BinaryOperator: 두 개의 동일한 유형의 값을 받아서 하나의 값을 반환하는 이항 연산자입니다.이 이항 연산자는 스트림의 요소를 결합하는 데 사용됩니다.

 

<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  • U: 축소된 결과의 유형을 나타냅니다.
  • identity: 초기값으로 사용될 값입니다.
  • BiFunction: 누적자와 요소를 받아서 새로운 누적자를 반환하는 함수입니다.
  • BinaryOperator<U>: 두 개의 동일한 유형의 값을 받아서 하나의 값을 반환하는 이항 연산자입니다.
    이 이항 연산자는 누적자를 결합하는 데 사용됩니다.

 

reduce() 메서드는 스트림의 모든 요소를 누적하여 하나의 값으로 줄입니다. 첫 번째 형태의 reduce() 메서드는 요소를 이항 연산자를 사용하여 순차적으로 결합합니다. 

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> sum = numbers.stream()
                               .reduce((a, b) -> a + b);

 

두 번째 형태의 reduce() 메서드는 초기값과 누적 함수를 사용하여 요소를 순차적으로 결합하고, 마지막으로 이항 연산자를 사용하여 다중 스레드에서 병렬로 결합합니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);

 

이항 연산자 (a, b) -> a + b는 두 개의 값을 더하는 람다 표현식으로, 요소를 순차적으로 더해 나가면서 최종 결과를 계산합니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b, (x, y) -> x + y);

 

첫 번째 파라미터로 초기값인 0이 전달되었고 두 번째 파라미터로 (a, b) -> a + b 람다 표현식인 누적 함수가 전달되었습니다. 이 누적 함수는 각 요소를 순차적으로 더해 나가면서 새로운 누적자를 반환합니다.세 번째 파라미터로 (x, y) -> x + y 람다 표현식인 이항 연산자인 결합 함수가 전달되었습니다. 이 결합 함수는 다중 스레드에서 병렬로 실행될 때, 부분 결과를 안전하게 결합하는 데 사용됩니다.

 

 

FindFirst,FindAny

Optional<T> findFirst()
Optional<T> findAny()
  • 반환값은 Optional<T>입니다. 스트림에서 처음으로 발견된 요소를 감싼 Optional 객체를 반환합니다.

findFirst() 메서드는 스트림에서 첫 번째로 발견된 요소를 반환합니다. 이 때, 스트림의 요소가 정렬되어 있거나 첫 번째 요소가 명확하게 정의되어 있지 않을 경우에는 스트림의 첫 번째 요소를 반환합니다.

findAny() 메서드는 스트림에서 임의의 요소를 반환합니다. 병렬 처리와 관련하여 여러 스레드가 동시에 작업을 수행할 때 유용하며, 어떤 요소가 반환되는지에 대한 명확한 규칙은 없습니다.

 

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

Optional<String> firstElement = names.stream().findFirst();
Optional<String> anyElement = names.stream().findAny();

 

 

Match

anyMatch(Predicate<? super T> predicate)
allMatch(Predicate<? super T> predicate)
noneMatch(Predicate<? super T> predicate)
  • predicate: 요소를 조건부로 검사하는 Predicate입니다. Predicate는 함수형 인터페이스로, 주어진 요소에 대해 test() 메서드를 호출하여 참 또는 거짓을 반환합니다.
  • 반환값은 boolean입니다. 스트림에서 최소한 하나의 요소가 주어진 조건(predicate)을 만족하는지 여부를 나타냅니다.

 주어진 조건을 요소들에 대해 검사하여 부분적인 혹은 전체적인 일치 여부를 확인하는 데 사용됩니다

 

//숫자 리스트에서 양수인지 확인하는 경우
List<Integer> numbers = Arrays.asList(1, 2, -1, 3, -2, 4, 5);
boolean anyPositive = numbers.stream().anyMatch(n -> n > 0);
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);

 

 

 Count, Max, Min

long count()
  • 반환값은 스트림의 요소 개수를 나타내는 long 유형입니다.

 

Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
  • comparator: 요소를 비교하기 위한 Comparator입니다.
  • 반환값은 스트림에서 최대값 및 최소값을 감싼 Optional 객체를 반환합니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

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

 

 

 Sum, Avarage

double sum()
  • 반환값은 스트림의 요소들의 합계를 나타내는 double 유형입니다.
OptionalDouble average()
  • 반환값은 스트림의 요소들의 평균값을 감싼 OptionalDouble 객체를 반환합니다

 

sum() 메서드는 스트림의 요소들의 합계를 계산하여 반환합니다. 이는 스트림의 요소를 처리하며 합계를 계산하는 작업을 수행합니다.

average() 메서드는 스트림의 요소들의 평균값을 계산하여 반환합니다. 반환값은 OptionalDouble 객체로 감싸여 있으므로, 스트림이 비어있는 경우를 처리하기 위해 OptionalDouble을 사용하여 결과를 처리할 수 있습니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

double sum = numbers.stream().mapToInt(Integer::intValue).sum();
OptionalDouble average = numbers.stream().mapToDouble(Integer::doubleValue).average();

 

 

Collect

<R> R collect(Collector<? super T, A, R> collector)
  • R: 최종 수집 결과의 유형을 나타냅니다.
  • collector: 요소들을 수집하여 최종 결과를 생성하는 컬렉터입니다.

collect() 메서드는 스트림의 요소들을 컬렉터를 사용하여 수집하고, 최종 결과를 생성합니다. collector는 Collector 인터페이스를 구현한 객체로, 스트림의 요소를 수집하여 결과를 생성하는 데 사용됩니다. 컬렉터는 스트림 요소를 수집하기 위해 초기화 객체(A), 누적 함수, 병합 함수 등을 정의할 수 있습니다.

 

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Set<Integer> evenNumbers = numbers.stream()
                                  .filter(n -> n % 2 == 0)
                                  .collect(Collectors.toSet());

 

collect() 메서드는 스트림의 요소들을 수집하여 다양한 종류의 결과로 생성하는 데 유용합니다. 컬렉터를 사용하여 원하는 컬렉션 유형(List, Set, Map 등)이나 사용자 정의 컨테이너에 요소를 수집할 수 있습니다. 이를 통해 스트림의 요소를 집계하거나 변환하여 원하는 형태로 수집할 수 있습니다.

 

 

 

반응형

'컴퓨터 언어 > Java' 카테고리의 다른 글

JPA 기본 개념과 활용 방법  (0) 2023.08.29
Project Jigsaw 사용 방법  (0) 2023.07.09
Sliding Window 알고리즘  (0) 2023.04.27
BFS(너비 우선 탐색) 알고리즘  (0) 2023.04.27
Queue (큐)  (0) 2023.04.27
profile

소소한개발팁

@개발자 뱅

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!