본문 바로가기

Java

Java - Stream

Stream 이란?

자료 출처 : https://morioh.com/p/6b859b7a83e6

  • Java 8 에서 추가된 기능
  • 컬렉션 데이터를 선언형으로 쉽게 처리할 수 있다.
  • 루프문을 사용하지 않아도 되고, 루프문 중첩이 필요없다.
  • 병렬처리를 별도의 멀티 쓰레드 구현 없이 할 수 있다.

기본 스트림 생성

1. 컬렉션에서 스트림 생성

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); 
//list라는 List 컬렉션의 stream을 생성하는 것이라 list.stream()


2. 배열에서 스트림 생성

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
//배열이기 때문에, Arrays를 import한 뒤, Arrays.stream() 하고
//stream 소괄호 안에 스트림으로 만들 배열의 이름을 대입



3. 값 목록에서 스트림 생성

Stream<Integer> stream = Stream.of(1, 2, 3, 4);
더보기

3-1. 값 목록이란?

주로 여러 개의 값을 직접 나열할 때 사용되는 용어. 배열과 유사하지만, 일반적으로 고정된 크기의 배열과는 달리 다양한 데이터구조에서 사용될 수 있다.

 

예) 배열: 고정된 크기로 같은 데이터 타입의 요소를 저장하는 데이터 구조

String[] array = {"a", "b", "c"};

 

예) 값 목록: Stream.of() 메서드를 사용하여 임의의 개수의 값을 직접 나열하여 생성할수 있다.

Stream<String> stream = Stream.of("a", "b", "c");

중간 연산(Intermediate Operations)

중간 연산은 스트림을 변환하며, Lazy Evaluation 방식으로 작동한다.

 

더보기

Lazy Evaluation(지연평가)이란?

Lazy Evaluation은 값이 필요할 때까지 계산을 미루는 프로그래밍 기법. Java Stream  API 에서 이 방식은 여러 장점을 제공한다.

 

특징 및 장점:

  • 성능 최적화: 필요하지 않은 데이터를 미리 계산하지 않기 때문에 메모리 사용량이 줄어든다.
  • 무한 스트림: 지연 평가 덕분에 무한한 크기의 스트림을 만들 수 있다. 예를 들어, 무한한 피보나치 수열을 생성할 수 있다.
  • 파이프라인 최적화: 여러개의 중간 연산이 있을 경우, 최종 연산이 호출될 때까지 실제 연산이 수행되지 않고, 모든 연산이 하나의 패스로 통합되어 수행된다.

예시)

import java.util.stream.Stream;

public class LazyEvaluationExample {
    public static void main(String[] args) {
        // 무한 스트림 생성
        Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
        
        // 처음 10개 숫자만 선택하고 제곱한 값을 출력
        infiniteStream
            .limit(10) // 10개로 제한
            .map(n -> {
                System.out.println("Mapping: " + n);
                return n * n; // 제곱 계산
            })
            .forEach(System.out::println); // 결과 출력
    }
}

 

출력 결과)

Mapping: 0
0
Mapping: 1
1
Mapping: 2
4
Mapping: 3
9
Mapping: 4
16
Mapping: 5
25
Mapping: 6
36
Mapping: 7
49
Mapping: 8
64
Mapping: 9
81

 

위의 코드 예시에서 Stream.interate(0, n-> n+1)은 무한한 숫자 스트림을 생성한다. 그러나 limit(10)을 사용하여 10개로 제한했기 때문에, 그 숫자들이 실제로 필요할 때(map을 호출할 때)만 계산된다. 이로 인해 Mapping: n 이 출력되며, 각 숫자가 필요할 때마다 계산이 이루어진다.

 

요약

  • 목록은 여러 개의 값을 나열하여 사용하는 것이며, 배열과 비슷하지만 가변적이고 다양한 데이터 타입을 포함할 수 있다.
  • Lazy Evaluation은 필요한 시점까지 계산을 미루어 성능을 최적화하고, 메모리 사용을 줄이는 방식이다. 이를 통해 무한 스트림과 같은 다양한 기능을 사용할 수 있다.

 

1. map: 요소 변환

stream.map(String::toUpperCase);



2. filter: 조건에 맞는 요소 필터링

stream.filter(s -> s.startsWith("a"));



3. distinct: 중복 제거

stream.distinct();



4. sorted: 정렬

stream.sorted(); // 자연 정렬
stream.sorted(Comparator.reverseOrder()); // 역순 정렬



5. limit: 요소 수 제한

stream.limit(3);



6. skip: 요소 건너뛰기

stream.skip(2);



7. flatMap: 중첩 스트림을 평탄화

stream.flatMap(s -> Stream.of(s.split(",")));

 

최종 연산(Terminal Operations)

 

최종 연산은 스트림을 소비하고 결과를 생성한다.

1.forEach: 각 요소에 대한 작업 수행

stream.forEach(System.out::println);



2. collect: 스트림을 컬렉션으로 수집

List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
String result = stream.collect(Collectors.joining(", "));



3. count: 요소 수 세기

long count = stream.count();



4. anyMatch, allMatch, noneMatch: 조건에 따른 요소 확인(true, false 반환)

boolean anyMatch = stream.anyMatch(s -> s.startsWith("a"));
boolean allMatch = stream.allMatch(s -> s.length() >0);
boolean noneMatch - stream.noneMatch(s -> s.isEmpty());



5. findFirst, findAny: 요소 찾기

Optional<String> first = stream.findFirst();
Optional<String> any = stream.findAny();



6.reduce: 요소 집계

Optional<Ieteger> sum = stream.reduce(0, Integer::sum)

 

추가적인 메서드

1. peek: 중간에 요소 확인(디버깅용)

stream.peek.(System.out::println);



2. toArray: 배열로 변환

String[] array = stream.toArray(String[]::new);



예시 코드로 정리

import java.util.Arrays; // 배열과 컬렉션을 위한 임포트
import java.util.List; // List 인터페이스를 위한 임포트
import java.util.Set; // Set 인터페이스를 위한 임포트
import java.util.Optional; // Optional 클래스 임포트
import java.util.Comparator; // Comparator 인터페이스 임포트
import java.util.stream.Collectors; // Collectors 클래스 임포트
import java.util.stream.Stream; // Stream 인터페이스 임포트

public class StreamExample {
    public static void main(String[] args) {
        // 1. 컬렉션에서 스트림 생성
        List<String> list = Arrays.asList("a", "b", "c", "a"); // List 생성
        Stream<String> stream1 = list.stream(); // list라는 List 컬렉션의 stream을 생성

        // 2. 배열에서 스트림 생성
        String[] array = {"a", "b", "c"}; // 배열 생성
        Stream<String> stream2 = Arrays.stream(array); // Arrays.stream()을 통해 배열의 스트림 생성

        // 3. 값 목록에서 스트림 생성
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4); // 직접 나열한 값으로 스트림 생성

        // 중간 연산 (Intermediate Operations)

        // 1. map: 요소 변환
        List<String> upperCaseList = stream1.map(String::toUpperCase) // 대문자로 변환
            .collect(Collectors.toList()); // 결과를 List로 수집
        System.out.println("Mapped to Uppercase: " + upperCaseList); // 출력

// Mapped to Uppercase: [A, B, C, A]



        // 2. filter: 조건에 맞는 요소 필터링
        List<String> filteredList = upperCaseList.stream() // 스트림을 다시 생성
            .filter(s -> s.startsWith("A")) // "A"로 시작하는 이름만 남김
            .collect(Collectors.toList());
        System.out.println("Filtered List: " + filteredList); // 출력

// Filtered List: [A]

        // 3. distinct: 중복 제거
        List<String> distinctList = filteredList.stream()
            .distinct() // 중복된 이름 제거
            .collect(Collectors.toList());
        System.out.println("Distinct List: " + distinctList); // 출력

// Distinct List: [A]

        // 4. sorted: 정렬
        List<String> sortedList = distinctList.stream()
            .sorted() // 자연 정렬
            .collect(Collectors.toList());
        System.out.println("Sorted List: " + sortedList); // 출력

//Sorted List: [A]

        // 5. limit: 요소 수 제한
        List<String> limitedList = sortedList.stream()
            .limit(1) // 첫 1개 요소 선택
            .collect(Collectors.toList());
        System.out.println("Limited List: " + limitedList); // 출력

// Limited List: [A]

        // 6. skip: 요소 건너뛰기
        List<String> skippedList = sortedList.stream()
            .skip(1) // 첫 번째 요소를 건너뜀
            .collect(Collectors.toList());
        System.out.println("Skipped List: " + skippedList); // 출력

// Skipped List: [B, C, A]

        // 7. flatMap: 중첩 스트림을 평탄화
        Stream<String> nestedStream = Stream.of("a,b,c", "d,e,f"); // 중첩된 문자열 스트림
        List<String> flatMapList = nestedStream
            .flatMap(s -> Stream.of(s.split(","))) // 문자열을 분할하여 평탄화
            .collect(Collectors.toList());
        System.out.println("FlatMap List: " + flatMapList); // 출력

//FlatMap List: [a, b, c, d, e, f]

        // 최종 연산 (Terminal Operations)

        // 1. forEach: 각 요소에 대한 작업 수행
        System.out.print("ForEach Output: ");
        limitedList.forEach(System.out::print); // 각 요소 출력
        System.out.println(); // 줄바꿈

//ForEach Output: A

        // 2. collect: 스트림을 컬렉션으로 수집
        Set<String> collectedSet = sortedList.stream()
            .collect(Collectors.toSet()); // Set으로 수집
        System.out.println("Collected Set: " + collectedSet); // 출력

//Collected Set: [A]

        // 3. count: 요소 수 세기
        long count = sortedList.stream()
            .count(); // 남은 요소 수
        System.out.println("Count of Sorted List: " + count); // 출력

// Count of Sorted List: 3

        // 4. anyMatch, allMatch, noneMatch: 조건에 따른 요소 확인
        boolean anyMatch = sortedList.stream().anyMatch(s -> s.startsWith("A")); // "A"로 시작하는 요소가 있는지 확인
        boolean allMatch = sortedList.stream().allMatch(s -> s.length() > 0); // 모든 요소가 길이가 0보다 큰지 확인
        boolean noneMatch = sortedList.stream().noneMatch(s -> s.equals("Z")); // "Z"가 없는지 확인
        System.out.println("Any Match (starts with A): " + anyMatch); // 출력
        System.out.println("All Match (length > 0): " + allMatch); // 출력
        System.out.println("None Match (equals Z): " + noneMatch); // 출력

//Any Match (starts with A): true
//All Match (length > 0): true
//None Match (equals Z): true

        // 5. findFirst, findAny: 요소 찾기
        Optional<String> firstElement = sortedList.stream().findFirst(); // 첫 번째 요소 찾기
        Optional<String> anyElement = sortedList.stream().findAny(); // 임의의 요소 찾기
        System.out.println("First Element: " + firstElement.orElse("None")); // 출력
        System.out.println("Any Element: " + anyElement.orElse("None")); // 출력

// First Element: A

// Any Element: A

        // 6. reduce: 요소 집계
        Optional<Integer> sum = Stream.of(1, 2, 3, 4) // 예시를 위해 다시 스트림 생성
            .reduce(0, Integer::sum); // 합계 계산
        System.out.println("Sum: " + sum.orElse(0)); // 출력

// Sum: 10

        // 추가적인 메서드

        // 1. peek: 중간에 요소 확인(디버깅용)
        List<String> peekedList = sortedList.stream()
            .peek(System.out::println) // 각 요소 출력
            .collect(Collectors.toList()); // 결과를 List로 수집
        System.out.println("Peeked List: " + peekedList); // 출력

// Peeked List: [A]

        // 2. toArray: 배열로 변환
        String[] arrayResult = sortedList.stream().toArray(String[]::new); // List를 배열로 변환
        System.out.println("Array Result: " + Arrays.toString(arrayResult)); // 출력
    }

//Array Result: [A]
}

아래는 위의 내용에서 애매하거나 잘 모르는 내용을 한번 더 알아보기 위해 기록해둔 내용이다.

Optional<String> 이란?

Optional은 Java 8 에서 도입된 클래스이다. 주로 null을 반환할 가능성이 있는 메서드에서 null 처리 문제를 해결하기 위해 사용된다. 즉, 값이 있을 수도 있고 없을 수도 있는 경우를 다루기 위한 용도로 사용된다.

특징
1. Optional 객체는 값을 포함할 수 있으며, 값이 없을 때는 empty 상태이다.
2. isPresent() 메서드를 사용하여 값이 존재하는지 확인할 수 있다.
3. get() 메서드를 호출하여 실제 값을 가져올 수 있찌만, 값이 없을 경우
NoSuchElementException 이 발생한다.
4. orElse(T other) 메서드를 사용하면 값이 없을 경우 기본값을 지정할 수 있다.

예제)

Optional<String> optional = Optional.ofNullable("Hello");
String value = optional.orElse("Default"); // "Hello"가 존재하므로 "Hello"가 반환됨


split 메서드 사용법?

split 메서드는 문자열을 특정 구분자로 나누어 배열로 반환하는 메서드. 주로 String 클래스에 정의되어 있다.

사용법예제)

String str = "a,b,c";
String[] parts = str.split(","); // 쉼표로 나누기
// 결과: parts는 ["a", "b", "c"]가 됨


split 메서드는 정규 표현식을 사용하기 때문에, 다양한 구분자를 사용할 수 있다.

예를 들어, 공백이나 다른 특수문자를 사용할 수 있다.

reduce() 메서드?

스트림의 모든 요소를 하나로 집계하는 최종 연산이다. 주로 덧셈, 곱셈 등의 집계 작업에 사용된다.

매개변수 설명
1. 첫번째 인자(identity): 초기값이다. 집계가 시작될 때  사용할 기본값이다. 이 값은 결과에 항상 포함된다.
2. 두번째 인자(accumulator): 두개의 인자를 받아 집계 작업을 수행하는 함수를 나타낸다. 일반적으로 두 요소를 받아 하나의 요소로 합치는 방식이다.

예제)다음 코드는 숫자 1부터 4까지의 합계를 계산한다.

Optional<Integer> sum = Stream.of(1, 2, 3, 4)
    .reduce(0, Integer::sum); // 0부터 시작하여 각 요소를 더함
//여기서 Integer::sum 은 두개의 정수를 더하는 메서드를 참조한다.


sum.orElse(0)의 의미

sum.orElse(0)은 Optional 객체에서 값을 가져오는 방법이다. 이 경우, sum이 null인 경우 (즉, 스트림이 비어 있을 경우) 기본값으로 0을 반환한다.

  • 만약 sum이 값이 있다면 그 값을 반환한다.
  • 만약 값이 없다면 0을 반환한다.

예제)

int total = sum.orElse(0); // sum이 비어있으면 0, 값이 있으면 그 값을 반환



요약


Optional<String>: null 처리 문제를 해결하기 위한 클래스.
split 메서드: 문자열을 특정 구분자로 나누어 배열로 반환하는 메서드.
reduce(): 스트림 요소를 집계하여 하나의 결과로 만드는 메서드.
sum.orElse(0): Optional에서 값을 가져오되, 값이 없으면 기본값인 0을 반환하는 방식.

 

위의 예제의 출력 결과에서, [A]인 경우와 A 인경우, 어떤 차이가 있을까?


결과에서 []가 포함된 경우와 그렇지 않은 경우의 차이는, Java에서 출력하는 객체의 형태에 따라 달라진다

 

1. List와 Set
*List: 순서가 있는 요소의 집합이다. 중복된 요소를 허용한다. List를 출력할 때, 요소가
존재하지 않으면 [] 가 나타난다. 예를 들어 비어있는 List는 []로 출력된다.

List<String> emptyList = new ArrayList<>();
System.out.println(emptyList); // 결과: []

*Set: 순서가 없고 중복을 허용하지 않는 집합이다. 비어 있는 Set도 []로 출력된다.

Set<String> emptySet = new HashSet<>();
System.out.println(emptySet); // 결과: []

2. 단일 요소 출력
*forEach 메서드: 단일 요소를 출력할 때는 []없이 직접 값이 출력된다.
예를들어, 다음과 같이 출력하면 값이 직접 출력된다.

limitedList.forEach(System.out::print); // 출력: A
여기서는 리스트에 요소가 하나만 있으므로, 결과는 A이다.

3. Stream의 출력
*Stream의 collect 메서드: collect(Collectors.toList()) 또는 collect(Collectors.toSet()) 메서드를
사용한 경우, 결과가 List나 Set으로 수집되며, 이후 출력할 때[]가 포함된다.

List<String> collectedList = stream.collect(Collectors.toList());
System.out.println(collectedList); // 결과: [A]

요약
[]가 포함된 경우: 결과가 List나 Set과 같은 컬렉션일 때 나타나며, 요소가 포함된 경우 [] 안에 요소가 나열된다.
[]가 없는 경우: 단일 요소를 직접 출력하거나, forEach와 같은 메서드를 사용할 때 나타나며, 각 요소가 직접 출력된다.

 

728x90
반응형

'Java' 카테고리의 다른 글

Java - 추상화  (2) 2024.09.23
Java - 다형성  (6) 2024.09.22
Java - 캡슐화  (1) 2024.09.13
Java - 상속성  (0) 2024.09.11