Java

[Java] Stream(스트림) 정리하기 + Stream 활용 예제

bum0w0 2024. 7. 8. 19:10

Stream 이란?

Java 8에서 추가된 기능으로, 데이터 처리를 위한 연속적인 요소입니다. 기본적으로 스트림은 데이터 원본(컬렉션, 배열 등)으로부터 생성되며 람다를 활용하여 데이터를 처리합니다. 연속적인 요소라고 함은 연산이 파이프라인 형태로 구성될 수 있다는 의미입니다. 각 연산은 원본 스트림으로부터 새로운 스트림을 반환하며, 중간 연산(ex. map, filter, sorted)과 최종 연산(ex. forEach, collect, reduce)으로 나뉘게 됩니다.

 

Ex.

전체 -> 맵핑 -> 필터링 1 -> 필터링 2 -> 결과 만들기 -> 결과물

이렇게 원본 배열 또는 컬렉션 인스턴스에 함수 여러 개를 연산시키고 원하는 결과를 필터링하여 결과를 얻을 수 있습니다.

 

스트림을 이용하면 데이터 처리 과정을 간결하게 표현할 수 있으며, 함수형 프로그래밍 스타일의 장점을 활용하여 코드를 더욱 함수적이고 선언적으로 작성할 수 있습니다. 따라서 복잡한 코드를 작성하게 된다면 스트림을 통해 데이터를 처리하는 것은 매우 효율적입니다.

 

 

스트림의 특징

 

1. 데이터 소스를 변경하지 않는다.

스트림은 데이터를 읽기만 하며, 원본 데이터 소스를 변경하지 않습니다. 예를 들어, 배열이나 컬렉션에 대한 스트림을 생성하여 여러 연산을 수행해도, 원본 데이터는 그대로 유지됩니다. 원본 데이터의 수정이나 갱신은 이루어지지 않습니다.

2. 스트림은 일회용이다.

openJDK 팀에서 Stream을 만들 때 참여한 엔지니어의 말에 의하면 스트림은 조회하려는 원본 자원이 empty일 때의 처리 방법을 해결하기 힘들어 아예 한 번만 사용 가능하게 만들었다고 합니다. (스트림이 파이프라인 형태이기 때문에 일회용인 것이 당연한 것 같기도 합니다.)

3. 작업을 내부 반복으로 처리한다.

스트림은 내부 반복(Internal Iteration)을 사용하여 작업을 처리합니다. 스트림 API에서 제공하는 다양한 연산(예: 맵핑, 필터링, 정렬 등)을 사용하여 개발자가 직접 데이터를 내부적으로 처리하는 것을 의미합니다. 이는 병렬 처리로 성능을 향상시킬 수 있는 장점을 가지고 있습니다.

 

 

스트림 생성

1. 배열 스트림 - Arrays.stream( )

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);

 

2. 컬렉션 스트림 - 컬렉션 + .stream( )

// 컬렉션 : List, Stack, Queue, Set, Map
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream();

 

3. 스트림 빌더 - Stream.builder( )

// 직접 스트림을 생성
Stream.Builder<String> builderStream = Stream.<String>builder()
        .add("a")
        .add("b")
        .add("c")
        .build();

 

4. 람다식 - Stream.generate( ) / Stream.iterate( )

Stream<Double> randomStream = Stream.generate(() -> Math.random())
	.limit(5); // 0에서 1 사이의 랜덤한 값 5개 생성
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2)
        .limit(5); // 0부터 시작하여 2씩 증가하는 짝수 5개 생성

 

5. 기본 자료형 스트림 - IntStream, DoubleStream, LongStream 세 가지 존재

IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]

DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0, 4.0, 5.0);

LongStream longStream = LongStream.iterate(0L, n -> n + 1)
        .limit(5); // 0부터 시작하여 1씩 증가하는 5개의 long 값 생성

 

 

중간 연산

1. Filtering (조건에 맞는 요소만 선택하여 새로운 스트림을 생성하는 연산)

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0); // 짝수만 걸러내는 예제

 

2. Mapping (각 요소를 변환하여 새로운 스트림을 생성하는 연산)

Stream<String> names = Stream.of("Kim", "Choi", "Park");
Stream<Integer> nameLengths = names.map(String::length); // 각 이름의 길이로 맵핑하는 예제

 

3. Sorting (스트림의 요소를 정렬하는 연산)

Stream<String> names = Stream.of("John", "Alice", "Bob");
// Comparator를 사용하여 길이에 따라 정렬
Stream<String> sortedNames = names.sorted(Comparator.comparing(String::length));
sortedNames.forEach(System.out::println); // 결과 출력: Bob, John, Alice

 

 

최종 연산

CalCulating

IntStream stream = list.stream()
    .count()   //스트림 요소 개수 반환
    .sum()     //스트림 요소의 합 반환
    .min()     //스트림의 최소값 반환
    .max()     //스트림의 최대값 반환
    .average() //스트림의 평균값 반환

 

Reduction

// 스트림의 모든 요소에 대해 연산을 수행하고 그 결과를 하나의 값으로 도출 (ex.누적 연산)
Optional<Integer> sum = stream.reduce((a, b) -> a + b);

 

Collecting 

// 스트림의 요소들을 수집하여 다양한 방식으로 처리할 수 있다. 
// Collectors 클래스의 정적 메소드를 사용하여 리스트, 맵, 셋 등으로 변환

List<String> collectedList = stream.collect(Collectors.toList());

Set<String> collectedSet = stream.collect(Collectors.toSet());

Map<Integer, String> collectedMap = stream.collect(Collectors.toMap(String::length, s -> s));

// joining: 하나의 문자열로 결합
String joinedString = stream.collect(Collectors.joining(", "));

// groupingBy: 스트림 요소를 기준에 따라 그룹화
Map<Integer, List<String>> groupedMap = stream.collect(Collectors.groupingBy(String::length));

 

Matching

// anyMatch: 스트림의 요소 중 하나라도 주어진 조건을 만족하는지 확인
boolean anyMatch = stream.anyMatch(s -> s.startsWith("A"));

// allMatch: 스트림의 모든 요소가 주어진 조건을 만족하는지 확인
boolean allMatch = stream.allMatch(s -> s.length() > 3);

// noneMatch: 스트림의 모든 요소가 주어진 조건을 만족하지 않는지 확인
boolean noneMatch = stream.noneMatch(s -> s.contains("X"));

 

Iterating

// forEach: 스트림의 각 요소에 대해 주어진 동작을 수행
stream.forEach(System.out::println);

 

Finding

// findFirst: 스트림의 첫 번째 요소를 반환
Optional<String> firstElement = stream.findFirst();

 

 

 

이 밖에도 중간 연산 메소드와 최종 연산 메소드를 더 확인하고 싶다면 아래 문서를 살펴보기 바랍니다. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

 

Stream (Java Platform SE 8 )

A sequence of elements supporting sequential and parallel aggregate operations. The following example illustrates an aggregate operation using Stream and IntStream: int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight())

docs.oracle.com

 

 

 

스트림 활용 예제

프로그래머스 Lv.2 '의상' 문제

 

문제설명

더보기

코니는 매일 다른 옷을 조합하여 입는것을 좋아합니다.

예를 들어 코니가 가진 옷이 아래와 같고, 오늘 코니가 동그란 안경, 긴 코트, 파란색 티셔츠를 입었다면 다음날은 청바지를 추가로 입거나 동그란 안경 대신 검정 선글라스를 착용하거나 해야합니다.

  • 코니는 각 종류별로 최대 1가지 의상만 착용할 수 있습니다. 예를 들어 위 예시의 경우 동그란 안경과 검정 선글라스를 동시에 착용할 수는 없습니다.
  • 착용한 의상의 일부가 겹치더라도, 다른 의상이 겹치지 않거나, 혹은 의상을 추가로 더 착용한 경우에는 서로 다른 방법으로 옷을 착용한 것으로 계산합니다.
  • 코니는 하루에 최소 한 개의 의상은 입습니다.
  • 코니가 가진 의상들이 담긴 2차원 배열 clothes가 주어질 때 서로 다른 옷의 조합의 수를 return 하도록 solution 함수를 작성해주세요.

제한사항

  • clothes의 각 행은 [의상의 이름, 의상의 종류]로 이루어져 있습니다.
  • 코니가 가진 의상의 수는 1개 이상 30개 이하입니다.
  • 같은 이름을 가진 의상은 존재하지 않습니다.
  • clothes의 모든 원소는 문자열로 이루어져 있습니다.
  • 모든 문자열의 길이는 1 이상 20 이하인 자연수이고 알파벳 소문자 또는 '_' 로만 이루어져 있습니다.

 

import java.util.*;
import static java.util.stream.Collectors.*;

class Solution {
    public int solution(String[][] clothes) {
        return Arrays.stream(clothes)
                .collect(groupingBy(p -> p[1], mapping(p -> p[0], counting())))
                .values()
                .stream()
                .collect(reducing(1L, (x, y) -> x * (y + 1)))
                .intValue() - 1;
    }
}

1. Arrays.stream(clothes): clothes 배열을 스트림으로 변환합니다.

2. .collect(groupingBy(p -> p[1], mapping(p -> p[0], counting()))): 의상의 종류별로 그룹화하여 각 종류의 의상 개수를 센 후 맵으로 변환합니다.

3. .values().stream(): 그룹화된 의상 개수 맵의 값들을 스트림으로 변환합니다.

4. .collect(reducing(1L, (x, y) -> x * (y + 1))): 각 종류의 의상 개수에 1을 더한 값을 모두 곱하여 가능한 모든 조합을 계산합니다.

5. .intValue() - 1: 계산된 값에서 아무것도 입지 않은 경우를 제외하기 위해 1을 뺍니다.

 

 

 

 

 

 

참고 자료 출처 :

https://velog.io/@yun8565/Java-스트림Stream-정리

https://futurecreator.github.io/2018/08/26/java-8-streams/