filter()
, map()
)은 실제로 종료 연산(예: forEach()
, collect()
)이 호출될 때까지 실행되지 않으므로, 필요한 데이터만 처리하는 효율성을 갖습니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("K")) {
result.add(name.toUpperCase());
}
}
System.out.println(result); // 출력: [KIM]
List<String> names = Arrays.asList("Kim", "Lee", "Park");
List<String> result = names.stream()
.filter(name -> name.startsWith("K"))
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result); // 출력: [KIM]
parallelStream()
을 사용하면 자동으로 데이터를 병렬로 처리할 수 있습니다.filter()
, map()
)은 즉시 실행되지 않고, 최종 연산(Terminal Operations)이 호출될 때 실행됩니다.parallelStream()
을 사용하면 데이터를 병렬로 처리할 수 있습니다.Collection
인터페이스에는 stream()
과 parallelStream()
메서드가 추가되어있어서, 이를 통해 쉽게 Stream을 만들 수 있습니다.stream()
메서드를 호출하여 Stream을 생성하고, 필요한 작업을 적용할 수 있습니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
// 스트림 생성
Stream<String> nameStream = names.stream();
// 스트림을 사용한 데이터 처리
nameStream.filter(name -> name.startsWith("K"))
.forEach(System.out::println); // 출력: Kim
names.stream()
을 통해 리스트로부터 스트림을 생성하고, filter()
와 forEach()
를 사용하여 데이터를 처리했습니다..filter().forEach()
처럼 한줄에 메서드를 이어 붙이는 것을 메서드 체이닝(Method Chaining)이라고 합니다.Arrays.stream()
메서드를 사용하여 Stream을 생성할 수 있습니다. - 배열을 직접 Stream으로 변환할 수 있는 방법으로 제공됩니다.String[] nameArray = {"Kim", "Lee", "Park"};
// 배열로부터 스트림 생성
Stream<String> nameStream = Arrays.stream(nameArray);
// 스트림을 사용한 데이터 처리
nameStream.filter(name -> name.startsWith("P"))
.forEach(System.out::println); // 출력: Park
Stream.of()
메서드를 사용하여 배열을 Stream으로 변환할 수도 있습니다.Stream<String> stream = Stream.of("Kim", "Lee", "Park");
stream.forEach(System.out::println);
java.nio.file
)를 사용하면, 파일에서 Stream을 생성하여 각 줄을 읽고 처리할 수 있습니다.Path path = Paths.get("file.txt");
// 파일에서 스트림 생성
Stream<String> lines = Files.lines(path);
// 스트림을 사용한 데이터 처리
lines.filter(line -> line.contains("Java"))
.forEach(System.out::println);
file.txt
파일의 각 줄을 스트림으로 읽어, "Java"라는 단어가 포함된 줄을 출력합니다.IntStream
, LongStream
, DoubleStream
과 같은 숫자형 스트림을 제공하여, 기본형 (원시) 타입의 숫자를 효율적으로 처리할 수 있습니다. range()
, rangeClosed()
와 같은 메서드를 사용하여 생성할 수 있습니다.IntStream.range(1, 5) // 1, 2, 3, 4
.forEach(System.out::println);
rangeClosed()
메서드로 사용하시면 됩니다.메서드 | 설명 | 반환 타입 | 사용 예시 |
---|---|---|---|
filter() | 조건에 맞는 요소만 필터링합니다. | Stream<T> | stream.filter(x -> x > 10) |
map() | 각 요소를 주어진 함수에 따라 변환합니다. | Stream<R> | stream.map(String::toUpperCase) |
flatMap() | 각 요소를 스트림으로 변환하고, 이를 하나의 스트림으로 평탄화합니다. | Stream<R> | stream.flatMap(List::stream) |
distinct() | 중복된 요소를 제거합니다. | Stream<T> | stream.distinct() |
sorted() | 스트림의 요소를 정렬합니다. | Stream<T> | stream.sorted( Comparator.reverseOrder()) |
peek() | 각 요소를 소비하지 않고 보기만 합니다. 디버깅이나 로그를 출력할 때 유용합니다. | Stream<T> | stream.peek(System.out::println) |
limit() | 스트림에서 처음 n개의 요소만 반환합니다. | Stream<T> | stream.limit(5) |
skip() | 처음 n개의 요소를 건너뜁니다. | Stream<T> | stream.skip(3) |
takeWhile() | 조건이 참인 동안 스트림의 요소를 계속 반환합니다. (Java 9 이상) | Stream<T> | stream.takeWhile(x -> x < 10) |
dropWhile() | 조건이 참인 동안 스트림의 요소를 건너뛰고, 조건이 거짓이 될 때부터 남은 요소를 반환합니다. (Java 9 이상) | Stream<T> | stream.dropWhile(x -> x < 10) |
filter()
는 주어진 조건에 맞는 요소만을 선택하여 스트림을 필터링하는 중간 연산입니다.Predicate<T>
Predicate<T>
는 하나의 인수를 받아서 boolean
값을 반환하는 함수형 인터페이스입니다.List<String> names = Arrays.asList("Kim", "Lee", "Park", "Choi");
names.stream()
.filter(name -> name.startsWith("K"))
.forEach(System.out::println); // 출력: Kim
map()
은 각 요소를 주어진 함수에 따라 변환하는 중간 연산입니다.Function<T, R>
Function<T, R>
는 T 타입의 입력을 받아 R 타입의 결과를 반환하는 함수형 인터페이스입니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // 출력: KIM, LEE, PARK
flatMap()
은 각 요소를 스트림으로 변환하고, 여러 스트림을 하나로 평탄화하여 하나의 스트림으로 반환합니다.Function<T, Stream<R>>
flatMap()
은 각 요소를 스트림으로 변환한 후, 그 스트림을 하나의 스트림으로 평탄화하는 Function<T, Stream<R>>
을 인수로 받습니다.List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e", "f")
);
listOfLists.stream()
.flatMap(List::stream)
.forEach(System.out::println); // 출력: a, b, c, d, e, f
distinct()
는 중복된 요소를 제거하는 중간 연산입니다.List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
numbers.stream()
.distinct()
.forEach(System.out::println); // 출력: 1, 2, 3, 4, 5
sorted()
는 스트림의 요소를 정렬하는 중간 연산입니다.Comparator<T>
or 없음sorted()
는 요소를 정렬할 때 Comparator<T>
를 인수로 받을 수 있습니다. Comparator<T>
는 두 개의 인수를 비교하여 정렬 순서를 결정합니다.Comparator
가 이용됩니다.List<String> names = Arrays.asList("Lee", "Kim", "Park");
names.stream()
.sorted()
.forEach(System.out::println); // 출력: Kim, Lee, Park
peek()
는 스트림의 각 요소를 소비하지 않고, 중간에 검사하거나 디버깅 목적으로 사용할 수 있는 중간 연산입니다.Consumer<T>
peek()
는 각 요소를 "소비"하지 않고 보기만 하므로 Consumer<T>
를 인수로 받습니다. Consumer<T>
는 입력을 받아 처리하지만 반환값이 없는 함수형 인터페이스입니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
names.stream()
.peek(name -> System.out.println("Processing: " + name))
.map(String::toUpperCase)
.forEach(System.out::println); // 출력: Processing: Kim, KIM, Processing: Lee, LEE, Processing: Park, PARK
limit()
은 스트림의 처음 n개의 요소만 반환하고, skip()
은 처음 n개의 요소를 건너뛰는 중간 연산입니다.limit()
과 skip()
은 단순히 스트림의 요소를 제한하거나 건너뛰는 연산을 수행하며, 함수형 인터페이스를 사용하지 않습니다.IntStream.range(1, 10)
.limit(5)
.forEach(System.out::println); // 출력: 1, 2, 3, 4, 5
IntStream.range(1, 10)
.skip(5)
.forEach(System.out::println); // 출력: 6, 7, 8, 9
takeWhile()
은 조건이 참인 동안 요소를 반환하고, dropWhile()
은 조건이 참인 동안 요소를 건너뜁니다.Predicate<T>
takeWhile()
과 dropWhile()
은 조건을 만족하는 동안 요소를 반환하거나 건너뛰므로, Predicate<T>
를 인수로 받습니다.List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// takeWhile 사용 예시
numbers.stream()
.takeWhile(n -> n < 5)
.forEach(System.out::println); // 출력: 1, 2, 3, 4
// dropWhile 사용 예시
numbers.stream()
.dropWhile(n -> n < 5)
.forEach(System.out::println); // 출력: 5, 6, 7, 8, 9
List<String> names = Arrays.asList("Kim", "Lee", "Park");
List<String> result = names.stream()
.filter(name -> name.length() > 3) // 필터링
.map(String::toUpperCase) // 대문자로 변환
.collect(Collectors.toList()); // 종료 연산을 통해 리스트로 수집
System.out.println(result); // 출력: PARK
filter()
와 map()
은 중간 연산으로, 지연 처리되며, collect()
라는 종료 연산이 실행될 때 실제로 모든 연산이 수행됩니다.메서드 | 설명 | 반환 타입 | 사용 예시 |
---|---|---|---|
forEach() | 각 요소에 대해 주어진 동작을 수행하며, 반환값이 없습니다. | void | stream.forEach( System.out::println) |
forEachOrdered() | 스트림의 요소들을 순서대로 처리합니다 (병렬 스트림에서도 순서를 보장합니다). | void | stream.forEachOrdered( System.out::println) |
collect() | 스트림의 요소들을 다른 컬렉션이나 자료 구조로 변환하여 반환합니다. | Collector<T, A, R> | stream.collect( Collectors.toList()) |
reduce() | 스트림의 요소들을 결합하여 하나의 결과를 도출합니다. | Optional<T> | stream.reduce( (a, b) -> a + b) |
toArray() | 스트림의 요소들을 배열로 변환하여 반환합니다. | T[] | stream.toArray( String[]::new) |
min() | 스트림의 요소 중에서 최소값을 반환합니다. (Comparator를 인수로 받음) | Optional<T> | stream.min( Comparator.naturalOrder()) |
max() | 스트림의 요소 중에서 최대값을 반환합니다. (Comparator를 인수로 받음) | Optional<T> | stream.max( Comparator.naturalOrder()) |
count() | 스트림의 요소 개수를 반환합니다. | long | stream.count() |
anyMatch() | 스트림의 요소 중 하나라도 조건을 만족하는지 여부를 반환합니다. (Predicate를 인수로 받음) | boolean | stream.anyMatch( x -> x > 10) |
allMatch() | 스트림의 모든 요소가 조건을 만족하는지 여부를 반환합니다. (Predicate를 인수로 받음) | boolean | stream.allMatch( x -> x > 10) |
noneMatch() | 스트림의 모든 요소가 조건을 만족하지 않는지 여부를 반환합니다. (Predicate를 인수로 받음) | boolean | stream.noneMatch( x -> x > 10) |
findFirst() | 스트림의 첫 번째 요소를 반환합니다. | Optional<T> | stream.findFirst() |
findAny() | 스트림의 요소 중 하나를 반환합니다. (병렬 스트림에서는 아무 요소나 반환 가능) | Optional<T> | stream.findAny() |
Consumer<T>
forEach()
는 스트림의 각 요소에 대해 주어진 동작을 수행하며, 주로 데이터를 출력하거나 처리할 때 사용됩니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
// forEach()는 Consumer<T>를 인수로 받음
names.stream()
.forEach(System.out::println); // 출력: Kim, Lee, Park
Collector<T, A, R>
collect()
는 스트림의 요소들을 다른 자료 구조로 변환하여 반환합니다. Collectors
클래스를 통해 쉽게 구현할 수 있습니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
// collect()는 Collector<T, A, R>을 인수로 받음
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("K"))
.collect(Collectors.toList()); // List로 수집
System.out.println(filteredNames); // 출력: [Kim]
BinaryOperator<T>
reduce()
는 스트림의 요소들을 결합하여 하나의 결과를 도출하는 함수형 인터페이스입니다. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// reduce()는 BinaryOperator<T>를 인수로 받음
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
System.out.println(sum.get()); // 출력: 15
toArray()
는 스트림의 요소들을 배열로 변환하여 반환합니다. List<String> names = Arrays.asList("Kim", "Lee", "Park");
// toArray()는 스트림의 요소를 배열로 변환
String[] nameArray = names.stream()
.toArray(String[]::new);
System.out.println(Arrays.toString(nameArray)); // 출력: [Kim, Lee, Park]
Comparator<T>
min()
과 max()
는 스트림에서 최소값과 최대값을 찾는 메서드로, Comparator<T>
를 인수로 받습니다.List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// min()은 Comparator<T>를 인수로 받음
Optional<Integer> min = numbers.stream()
.min(Integer::compare);
System.out.println(min.get()); // 출력: 1
count()
는 스트림의 요소 개수를 반환합니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
// count()는 스트림의 요소 개수를 반환
long count = names.stream()
.count();
System.out.println(count); // 출력: 3
Predicate<T>
anyMatch()
는 스트림의 요소 중 하나라도 조건을 만족하는지 확인합니다.allMatch()
는 모든 요소가 조건을 만족하는지 확인합니다.noneMatch()
는 모든 요소가 조건을 만족하지 않는지를 확인합니다.List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// anyMatch()는 Predicate<T>를 인수로 받음
boolean hasEven = numbers.stream()
.anyMatch(x -> x % 2 == 0);
System.out.println(hasEven); // 출력: true
findFirst()
는 스트림의 첫 번째 요소를 반환하고, findAny()
는 스트림에서 아무 요소나 하나를 반환합니다. Optional<T>
로 값을 반환하므로, 값을 안전하게 처리할 수 있습니다.List<String> names = Arrays.asList("Kim", "Lee", "Park");
// findFirst()는 첫 번째 요소를 반환
Optional<String> first = names.stream()
.findFirst();
System.out.println(first.get()); // 출력: Kim
Stream<String> stream = Stream.of("Kim", "Lee", "Park");
// 종료 연산 후 스트림 재사용 불가
stream.forEach(System.out::println); // 스트림 사용
// 다음 코드는 에러 발생: IllegalStateException
stream.forEach(System.out::println); // 에러: 스트림이 이미 소모됨
parallelStream()
메서드를 사용하여 생성하거나, 기존 스트림에서 parallel()
메서드를 호출하여 변환할 수 있습니다. parallelStream()
메서드를 호출하여 병렬 처리를 시작할 수 있습니다.List<String> names = Arrays.asList("Kim", "Lee", "Park", "Choi");
// parallelStream()으로 병렬 스트림 생성
names.parallelStream()
.forEach(System.out::println);
stream()
으로 생성한 스트림에 parallel()
메서드를 호출하여 병렬 처리로 전환할 수 있습니다.List<String> names = Arrays.asList("Kim", "Lee", "Park", "Choi");
// stream() 생성 후 parallel()로 병렬 스트림으로 변환
names.stream()
.parallel()
.forEach(System.out::println);
sequential()
메서드를 호출하여 다시 순차 스트림으로 변환할 수 있습니다.names.parallelStream()
.sequential() // 다시 순차 스트림으로 변환
.forEach(System.out::println);
parallelStream()
메서드를 호출하거나 parallel()
메서드를 호출하는 것만으로 병렬 처리를 적용할 수 있습니다.forEachOrdered()
를 사용해야 합니다.names.parallelStream()
.forEachOrdered(System.out::println); // 순서를 보장하며 병렬 처리
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 병렬 스트림에서 병렬 작업을 수행할 경우, 스레드 안전성을 고려해야 함
numbers.parallelStream()
.forEach(number -> {
synchronized (System.out) {
System.out.println(number);
}
});
Stream<String> stream = Stream.of("Kim", "Lee", "Park");
// 첫 번째 종료 연산 (정상 작동)
stream.forEach(System.out::println);
// 두 번째 종료 연산 (에러 발생)
stream.forEach(System.out::println); // IllegalStateException 발생
Collections.synchronizedList()
를 사용하거나, synchronized
블록을 사용하여 안전하게 처리할 수 있습니다.forEach()
대신 forEachOrdered()
를 사용하여 처리 순서를 보장할 수 있습니다.// 작은 데이터셋에서는 오히려 for-each가 더 나은 성능을 보일 수 있음
for (String name : names) {
System.out.println(name);
}
// 스트림을 과도하게 사용할 경우 오히려 성능이 떨어질 수 있음
names.stream()
.forEach(System.out::println);
// 불필요하게 복잡한 스트림 체이닝 (비효율적)
names.stream()
.filter(name -> name.length() > 3)
.map(String::toLowerCase)
.sorted()
.distinct()
.forEach(System.out::println);
// 간결한 스트림 처리 (효율적)
names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println);
Stream<String> stream = Stream.of("Kim", "Lee", "Park");
// 스트림을 재사용할 필요가 있다면, 새로운 스트림을 생성
Stream<String> newStream = Stream.of("Kim", "Lee", "Park");
newStream.forEach(System.out::println);
ArrayList
와 같은 랜덤 접근이 가능한 데이터 구조에서는 스트림 성능이 좋지만, LinkedList
와 같은 순차 접근이 필요한 구조에서는 성능이 떨어질 수 있습니다. List<String> arrayList = new ArrayList<>(Arrays.asList("Kim", "Lee", "Park"));
List<String> linkedList = new LinkedList<>(Arrays.asList("Kim", "Lee", "Park"));
// ArrayList는 랜덤 접근이 가능하여 스트림 성능이 좋음
arrayList.stream().forEach(System.out::println);
// LinkedList는 순차 접근이 필요하여 스트림 성능이 떨어질 수 있음
linkedList.stream().forEach(System.out::println);