java.util.stream (Java SE 17 & JDK 17)
자바 스트림 API는 자바 8부터 도입된 기능으로, 컬렉션이나 배열의 데이터를 선언형 방식으로 처리하고 함수형 프로그래밍 스타일로 다룰 수 있도록 설계되었습니다.
스트림은 데이터 소스를 변경하지 않고 일련의 데이터를 연속적으로 처리할 수 있는 추상화된 데이터 흐름입니다. 스트림 API를 사용하면 데이터를 필터링하고, 매핑하고, 줄이는 작업을 단계별로 지정할 수 있으며, 코드를 간결하게 유지하면서도 읽기 쉽게 만듭니다.
filter
, map
)은 최종 연산이 호출될 때까지 실행되지 않음. 스트림은 컬렉션, 배열, 파일 등 다양한 소스에서 생성할 수 있습니다. 예를 들어 stream()
메서드로 컬렉션에서 스트림을 생성하거나, Arrays.stream()
으로 배열에서 스트림을 생성합니다. Files.lines()
로 파일에서 한 줄씩 읽어 스트림을 만들 수도 있으며, Stream.of()
나 IntStream.range()
로 특정 범위의 값을 가진 스트림을 생성할 수 있습니다.
List / Map / Set → 스트림 변경 가능
List
, Map
, Set
등 컬렉션은 stream()
메서드를 통해 스트림으로 변환할 수 있습니다.List<String> list = Arrays.asList("a", "b", "c"); list.stream();
배열
Arrays.stream()
또는 Stream.of()
메서드를 사용하여 배열로부터 스트림을 생성할 수 있습니다.String[] array = {"a", "b", "c"}; Stream<String> stream = Arrays.stream(array);
문자열
chars()
또는 split()
을 사용하여 문자열을 스트림으로 변환할 수 있습니다."hello".chars().mapToObj(c -> (char) c);
또는 "hello world".split(" ");
특정 범위 정수
IntStream.range()
나 IntStream.rangeClosed()
메서드를 사용하여 특정 범위의 정수 스트림을 생성할 수 있습니다.IntStream.range(1, 5); // 1, 2, 3, 4
난수
Random
클래스의 ints()
, doubles()
, longs()
메서드를 통해 무작위 스트림을 생성할 수 있습니다.new Random().ints(5); // 정수 난수 스트림 5개
람다
Stream.generate()
또는 Stream.iterate()
메서드를 사용하여 람다 표현식 기반 스트림을 생성할 수 있습니다.Stream.generate(() -> "element").limit(5); // 같은 문자열 "element"가 5번
빈 스트림
Stream.empty()
메서드를 통해 빈 스트림을 생성할 수 있습니다.Stream<Object> emptyStream = Stream.empty();
// List를 Stream으로 변환
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream(); // List에서 Stream 생성
// 배열을 Stream으로 변환
String[] nameArray = {"Alice", "Bob", "Charlie"};
Stream<String> nameArrayStream = Arrays.stream(nameArray); // 배열에서 Stream 생성
// 파일을 읽어 Stream으로 변환
Stream<String> lines = Files.lines(Paths.get("file.txt")); // 파일의 각 줄을 스트림으로 처리
// 직접 값을 전달하여 Stream 생성
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5); // 정수 스트림 생성
스트림 연산은 중간 연산과 최종 연산으로 나눌 수 있습니다. 중간 연산은 새로운 스트림을 반환하여 연속적인 처리가 가능하고, 최종 연산은 스트림을 소비하여 결과를 반환합니다.
filter
(조건에 맞는 요소 선택), map
(각 요소를 변환), sorted
(요소 정렬).// 중간 연산 (Intermediate Operations)
// filter: 조건에 맞는 요소만 남기는 연산 (이름이 "A"로 시작하는 요소)
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println); // "Alice", "Anna" 출력
// map: 각 요소를 변환하는 연산 (각 이름을 문자열의 길이로 변환)
names.stream()
.map(String::length)
.forEach(System.out::println); // 각 이름의 길이 출력 (5, 3, 7, 4)
// sorted: 요소를 정렬하는 연산
names.stream()
.sorted()
.forEach(System.out::println); // 알파벳 순서대로 출력 (Alice, Anna, Bob, Charlie)
forEach
(각 요소를 소비), collect
(컬렉션으로 변환), reduce
(모든 요소를 결합하여 하나의 결과 반환), count
(요소 개수 반환).// 최종 연산 (Terminal Operations)
// forEach: 각 요소를 소비하는 연산 (모든 이름 출력)
names.stream()
.forEach(System.out::println);
// collect: 스트림의 결과를 컬렉션으로 변환하는 연산 (이름이 "A"로 시작하는 요소를 리스트로 수집)
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
System.out.println(result); // [Alice, Anna] 출력
// reduce: 모든 요소를 결합하여 하나의 결과를 반환 (숫자의 합 계산 예시)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); // 초기값 0에서 시작하여 모든 요소 합산
System.out.println(sum); // 15 출력
// count: 요소의 개수를 반환 (이름이 "A"로 시작하는 요소의 개수)
long count = names.stream()
.filter(name -> name.startsWith("A"))
.count();
System.out.println(count); // 2 출력
연산 유형 | 메서드 예시 | 설명 |
---|---|---|
중간 연산 | filter , map , sorted | 요소 변환 및 필터링, 중간 스트림 반환 |
최종 연산 | forEach , collect , reduce , count | 스트림을 소비하여 결과 반환 |
병렬 처리 지원 | parallelStream | 스트림을 병렬 처리로 전환하여 멀티코어 활용 |
// 이름이 "A"로 시작하는 요소만 필터링, 정렬 후 출력
names.stream()
.filter(name -> name.startsWith("A")) // "A"로 시작하는 이름 필터링
.sorted() // 알파벳 순서대로 정렬
.forEach(System.out::println); // 필터링된 이름을 출력
// 숫자 리스트를 Stream으로 처리하여 제곱의 합 계산
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sumOfSquares = numbers.stream()
.map(n -> n * n) // 각 숫자를 제곱
.reduce(0, Integer::sum); // 제곱한 값을 모두 더함
System.out.println(sumOfSquares); // 제곱의 합 출력
import java.util.Arrays;
import java.util.stream.Stream;
public class StreamEx01 {
public static void main(String[] args) {
// 데이ㅓ 생성
String[] nameArr = { "IronMan", "Captain", "SpiderMan", "Thor" };
// 데이터 처리 - 배열을 정렬
Arrays.sort(nameArr);
// 데이터 출력 - 정렬된 배열을 반복문으로 출력
for (String name : nameArr) {
System.out.println(name);
}
// Stream 생성
Stream<String> nameStream = Arrays.stream(nameArr);
// Stream 처리 - 정렬된 데이터를 출력
nameStream.sorted().forEach(System.out::println);
}
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamEx02 {
public static void main(String[] args) {
// 스트림 생성 예시
// 1. List를 Stream으로 변환
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.stream();
// forEach를 사용하여 스트림의 요소를 출력
intStream.forEach(System.out::println);
// 2. Stream.of() 메서드를 사용하여 스트림 생성 - 문자열 스트림 생성
Stream<String> strStream = Stream.of("a", "b", "c");
strStream.forEach(System.out::println); // 스트림의 문자열 요소를 출력
// 3. 빈 스트림 생성
Stream<Integer> emptyStream = Stream.empty(); // 빈 스트림 생성
System.out.println("출력"); // 빈 스트림에는 출력할 요소가 없음
}
}
import java.util.stream.IntStream;
public class StreamEx03 {
public static void main(String[] args) {
// 1 ~ 10까지의 숫자를 포함하는 IntStream 생성
IntStream test1 = IntStream.rangeClosed(1, 10);
// test1.forEach(System.out::println); // 스트림의 모든 숫자 출력
// 짝수만 필터링하여 출력
// test1.filter(i -> i % 2 == 0).forEach(System.out::println);
// 여러 개의 숫자로 이루어진 IntStream 생성
IntStream test2 = IntStream.of(1, 2, 3, 3, 2, 5, 7, 6, 9);
// 중복 제거 후 각 요소 출력
test2.distinct().forEach(System.out::println); // 중복을 제거한 각 숫자 출력
}
}
public class StreamEx04 {
public static void main(String[] args) {
// 문자열 스트림 생성
Stream<String> strStream = Stream.of("b", "cc", "D", "C", "AA", "F");
// 오름차순 정렬
// strStream.sorted().forEach(System.out::println);
// 오름차순 정렬 - compareTo 메서드 사용
// strStream.sorted((s1, s2) -> s1.compareTo(s2)).forEach(System.out::println);
// 내림차순 정렬 - compareTo 메서드를 반대로 사용
// strStream.sorted((s1, s2) -> s2.compareTo(s1)).forEach(System.out::println);
// 내림차순 정렬 - Comparator.reverseOrder() 사용
// strStream.sorted(Comparator.reverseOrder()).forEach(System.out::println);
// 대소문자 구분 없이 오름차순 정렬
strStream.sorted(String.CASE_INSENSITIVE_ORDER)
.forEach(System.out::println); // 대소문자 무시하고 정렬 후 출력
}
}
import java.io.File;
import java.util.stream.Stream;
public class StreamEx05 {
public static void main(String[] args) {
// File 객체 배열 생성
File file1 = new File("ext1.java");
File file2 = new File("ext1.bak");
File file3 = new File("ext2.java");
File file4 = new File("ext3");
File file5 = new File("ext1.txt");
// File 객체들을 배열로 생성
File[] fileArr = {file1, file2, file3, file4, file5};
// 배열을 Stream으로 변환
Stream<File> fileStream = Stream.of(fileArr);
fileStream
.map(File::getName) // File 객체에서 파일명 추출
.filter(name -> name.endsWith(".java")) // 확장자가 ".java"인 파일만 필터링
.forEach(System.out::println); // 필터링된 결과를 출력
System.out.println("===========");
fileStream = Stream.of(fileArr);
fileStream.map(File::getName) // 파일명 추출
.filter(s -> s.indexOf('.') != -1) // 확장자가 없는 파일 제외 ('.'이 있는 파일만 필터링)
.map(s -> s.substring(s.indexOf('.') + 1)) // 확장자 부분만 추출
.distinct() // 중복 제거
.forEach(System.out::println); // 결과 출력
}
}
// 확장자를 추출 (확장자가 없는 것은 제외, 중복 제거)
fileStream.map(File::getName)
.filter(s -> s.indexOf(".") != -1)
.peek( s -> System.out.println("peek : " + s ))
.map(s -> s.substring(s.indexOf('.') + 1))
.peek( s -> System.out.println("peek : " + s ))
.distinct()
.forEach(System.out::println);
}