프로그래밍 공부
[자바] #22. 스트림(Stream) - 3편 (최종 연산 추가 내용) 본문
📚 최종 연산
최종 연산은 스트림 처리의 마지막 단계에서 결과를 반환하거나 처리하는 연산.
최종 연산이 호출될 때 중간 연산이 실제로 실행된다.
1️⃣ sum()
스트림 요소의 합계 반환.
📈 적용 대상: IntStream, LongStream, DoubleStream
int sum = IntStream.of(1, 2, 3, 4, 5).sum(); // 출력: 15
2️⃣ count()
스트림의 요소 개수 반환. (long 타입으로 반환)
long count = Stream.of("a", "b", "c").count(); // 출력: 3
3️⃣ average()
스트림 요소의 평균값 반환. (OptionalDouble 타입으로 반환)
📈 적용 대상: IntStream, LongStream, DoubleStream
OptionalDouble avg = IntStream.of(1, 2, 3, 4, 5).average();
avg.ifPresent(System.out::println); // 출력: 3.0
// OptionalDouble 타입으로 반환되므로 ifPresent 사용 가능
4️⃣ min()
스트림 요소 중 최솟값 반환. (OptionalInt 또는 OptionalDouble 타입으로 반환)
OptionalInt min = IntStream.of(5, 2, 9).min();
min.ifPresent(System.out::println); // 출력: 2
5️⃣ max()
스트림 요소 중 최댓값 반환. (OptionalInt 또는 OptionalDouble 타입으로 반환)
OptionalInt max = IntStream.of(5, 2, 9).max();
max.ifPresent(System.out::println); // 출력: 9
6️⃣ forEach()
스트림의 각 요소에 대해 작업 수행. (순차적으로 처리)
Stream.of("Java", "Python", "C++").forEach(System.out::println);
7️⃣ allMatch()
모든 요소가 조건을 만족하는지 여부를 반환. (boolean 타입으로 반환)
boolean allEven = IntStream.of(2, 4, 6).allMatch(n -> n % 2 == 0); // 출력: true
8️⃣ anyMatch()
하나 이상의 요소가 조건을 만족하는지 여부를 반환. (boolean 타입으로 반환)
boolean hasNegative = Stream.of(1, -2, 3).anyMatch(n -> n < 0); // 출력: true
9️⃣ noneMatch()
모든 요소가 조건을 만족하지 않는지 여부를 반환. (boolean 타입으로 반환)
boolean noneEmpty = Stream.of("A", "B", "").noneMatch(String::isEmpty); // 출력: false
🔟 collect()
스트림의 요소를 컬렉션(List, Set, Map 등)으로 수집하거나 요약
📈 주요 메서드: Collectors.toList(), Collectors.toSet(), Collectors.joining() 등
List<String> list = Stream.of("Java", "Python").collect(Collectors.toList());
System.out.println(list); // 출력: [Java, Python]
Stream<String>의 요소들을 List<String>로 수집.
Collectors.toList()는 스트림을 리스트로 변환하는 역할을 한다.
Collectors.toSet()은 List<Set>으로 변한, Collectors.toMap()은 List<Map>으로 변환...
또, Collectors.joining(" ")은
스트림에 있는 문자열들을 공백 " "을 구분자로 사용하여 연결해서 하나의 문자열로 결합한다.
String sentence = Stream.of("Java", "is", "awesome")
.collect(Collectors.joining(" ")); // 문자열 연결
System.out.println(sentence); // 출력: Java is awesome
🚀 왜 collect() 메서드를 사용해야 할까?
✔ collect()가 필요한 이유:
- 스트림은 일회성이다.
- 스트림을 사용하여 데이터를 처리하고 나면 재사용할 수 없다.
- 스트림 결과를 컬렉션에 저장하거나 원하는 형태로 반환하기 위해 collect()가 필요
🌟 매개변수를 3개 받는 collect 메서드
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
✅ 각 매개변수의 역할:
| 매개변수 | 타입 | 역할 |
| supplier | Supplier<R> | 결과 컨테이너 생성 (예: ArrayList::new) |
| accumulator | BiConsumer<R, T> | 각 스트림 요소를 결과 컨테이너에 누적하는 로직 |
| combiner | BiConsumer<R, R> | 병렬 처리 시 부분 결과 병합하는 로직 (parallelStream 사용 시 중요) |
- collect() 메서드의 3개 매개변수 버전은 커스텀 수집 로직을 구현할 수 있게 해준다.
- Collectors.toList() 같은 기본 수집기 대신, 사용자 정의 수집 방식을 직접 정의할 수 있다.
- 병렬 스트림(parallelStream()) 사용 시 부분 결과를 결합하는 combiner가 필요.
🚀 예제: collect()를 사용하여 List 수집
import java.util.*;
import java.util.stream.*;
public class CustomCollectExample {
public static void main(String[] args) {
List<String> names = Stream.of("Alice", "Bob", "Charlie")
.collect(
ArrayList::new, // supplier: 새로운 ArrayList 생성
List::add, // accumulator: 각 요소를 리스트에 추가
List::addAll // combiner: 병렬 처리 시 부분 리스트 병합
);
System.out.println(names); // 출력: [Alice, Bob, Charlie]
}
}
📝 과정 설명:
1️⃣ supplier:
- ArrayList::new
- 결과를 담을 빈 리스트를 생성
2️⃣ accumulator:
- List::add
- 각 스트림 요소(Alice, Bob, Charlie)를 리스트에 추가
3️⃣ combiner:
- List::addAll
- 병렬 처리 시 부분적으로 생성된 리스트들을 하나로 병합
하지만 이 예제에서는 순차 스트림을 사용했기 때문에 combiner인 List::addAll은 실제로 호출되지 않는다 !!
⚡ 동일한 작업을 기본 수집기로 수행:
List<String> names = Stream.of("Alice", "Bob", "Charlie")
.collect(Collectors.toList());
System.out.println(names); // 출력: [Alice, Bob, Charlie]
🎨 예제: 문자열을 연결하는 커스텀 collect()
스트림의 문자열을 쉼표로 연결하는 예제.
import java.util.stream.*;
public class StringJoinCustomCollect {
public static void main(String[] args) {
String result = Stream.of("Java", "Python", "C++")
.collect(
StringBuilder::new, // supplier: StringBuilder 생성
(sb, str) -> sb.append(str).append(","), // accumulator: 문자열과 쉼표 추가
StringBuilder::append // combiner: 병렬 처리 시 StringBuilder 병합
)
.toString();
// 마지막 쉼표 제거
result = result.endsWith(",") ? result.substring(0, result.length() - 1) : result;
System.out.println(result); // 출력: Java,Python,C++
}
}
📝 과정 설명:
1️⃣ supplier:
- StringBuilder::new
- 문자열을 효율적으로 연결할 수 있는 StringBuilder를 생성
2️⃣ accumulator:
- (sb, str) -> sb.append(str).append(",")
- 각 문자열을 StringBuilder에 추가하면서, 쉼표로 구분
3️⃣ combiner:
- StringBuilder::append
- 병렬 처리 시 부분 문자열을 결합
🏃 예제: 병렬 스트림(parallelStream())에서 combiner 확인
import java.util.*;
import java.util.stream.*;
public class ParallelCollectExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
String result = numbers.parallelStream() // 병렬 스트림 사용
.collect(
StringBuilder::new,
(sb, num) -> sb.append(num).append(","),
(sb1, sb2) -> sb1.append(sb2) // 병합 과정
)
.toString();
result = result.endsWith(",") ? result.substring(0, result.length() - 1) : result;
System.out.println("결과: " + result); // 출력: (순서가 바뀔 수 있음, 예: 3,1,5,2,4)
}
}
⚡ 설명:
- parallelStream()을 사용하면 스트림이 멀티스레드 환경에서 처리된다.
- 각 스레드에서 부분적으로 StringBuilder를 만든 후, combiner가 이 부분 결과를 하나의 StringBuilder로 병합한다.
- 이 과정에서 순서가 바뀔 수 있음에 주의.
📝 과정 설명:
- 스트림의 각 요소(num)가 StringBuilder(sb)에 누적(append)된다.
- sb.append(num)로 요소를 문자열로 추가하고, .append(",")로 각 요소 뒤에 쉼표(,)를 붙인다.
- 병렬 스트림(parallelStream())은 작업을 여러 스레드에 분할하여 처리하는데,
- 각 스레드는 독립적인 StringBuilder(부분 결과)를 생성하고,
- combiner는 이러한 부분 결과(sb1, sb2)를 하나의 최종 결과로 병합한다.
'자바' 카테고리의 다른 글
| [자바] #24. I/O 스트림 (0) | 2025.03.01 |
|---|---|
| [자바] #23. 날짜/시간 관련 클래스들 (1) | 2025.02.23 |
| [자바] #21. 스트림(Stream) - 2편 (병렬 스트림, 중간 연산 추가 내용) (0) | 2025.02.21 |
| [자바] #20. 스트림(Stream) - 1편 (+ 오토 박싱 / 언박싱) (1) | 2025.02.21 |
| [자바] #19. Optional 총정리 (0) | 2025.02.20 |