Java 8부터 도입된 Stream API는 데이터의 시퀀스를 처리하기 위한 일련의 요소들의 연속적인 흐름이다.
Stream은 컬렉션, 배열 또는 I/O 자원과 같은 데이터 소스에서 데이터를 추상화하고, 데이터를 다루는 다양한 기능을 제공한다.
Stream은 데이터 소스로부터 데이터를 읽고, 변환하고, 조작하며, 결과를 수집할 수 있는 기능을 제공한다.
스트림은 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복(internal iteration)을 통해 작업을 수행한다.
스트림은 재사용이 가능한 컬렉션과는 달리 단 한 번만 사용할 수 있다.
스트림의 연산은 필터-맵(filter-map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화할 수 있다.
스트림은 데이터 소스에 대한 원본 데이터를 변경하지 않는다. 대신, 스트림은 요소들의 연속적인 흐름을 통해 변환된 데이터를 제공한다.
스트림은 parallelStream() 메소드를 통한 손쉬운 병렬 처리를 지원한다.
스트림은 병렬 처리를 위한 기능을 제공한다. 이는 멀티스레딩을 사용하여 데이터를 동시에 처리하고, 성능을 향상시킬 수 있다.
Stream API는 다음과 같이 세 가지 단계에 걸쳐서 동작한다.
스트림은 다양한 데이터 소스로부터 생성할 수 있다.
List<String> names = Arrays.asList("John", "Alice", "Bob", "David");
Stream<String> stream = names.stream();
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
Stream<String> stream = Stream.of("apple", "banana", "cherry");
스트림 API에 의해 생성된 초기 스트림은 중개 연산을 통해 또 다른 스트림으로 변환된다.
이러한 중개 연산은 스트림을 전달받아 스트림을 반환하므로, 중개 연산은 연속으로 연결해서 사용할 수 있다.
또한, 스트림의 중개 연산은 필터-맵(filter-map) 기반의 API를 사용함으로 지연(lazy) 연산을 통해 성능을 최적화할 수 있다.
스트림 API에서 사용할 수 있는 대표적인 중개 연산과 그에 따른 메소드는 아래와 같다.
filter()
, distinct()
map()
, flatMap()
limit()
, skip()
sorted()
peek()
List<String> names = Arrays.asList("John", "Alice", "Bob", "David");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(nameLengths); // 출력: [4, 5, 3, 5]
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 출력: [2, 4]
스트림 API에서 중개 연산을 통해 변환된 스트림은 마지막으로 최종 연산을 통해 각 요소를 소모하여 결과를 표시한다.
즉, 지연(lazy)되었던 모든 중개 연산들이 최종 연산 시에 모두 수행되는 것이다.
이렇게 최종 연산 시에 모든 요소를 소모한 해당 스트림은 더는 사용할 수 없게 된다.
스트림 API에서 사용할 수 있는 대표적인 최종 연산과 그에 따른 메소드는 다음과 같다.
forEach()
reduce()
findFirst()
, findAny()
anyMatch()
, allMatch()
, noneMatch()
count()
, min()
, max()
sum()
, average()
collect()
List<String> names = Arrays.asList("John", "Alice", "Bob", "David");
long count = names.stream()
.count();
System.out.println(count); // 출력: 4
List<String> names = Arrays.asList("John", "Alice", "Bob", "David");
String joinedNames = names.stream()
.collect(Collectors.joining(", "));
System.out.println(joinedNames); // 출력: John, Alice, Bob, David
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = numbers.stream()
.filter(n -> n > 3)
.findFirst();
result.ifPresent(System.out::println); // 출력: 4
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sumOfEvenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
System.out.println(sumOfEvenNumbers); // 출력: 30
List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant");
List<String> filteredWords = words.stream()
.filter(word -> word.length() >= 5)
.collect(Collectors.toList());
System.out.println(filteredWords); // 출력: [apple, banana, elephant]
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Alice", 30),
new Person("Bob", 20)
);
Optional<Person> personOptional = people.stream()
.filter(person -> person.getAge() > 25)
.findFirst();
personOptional.ifPresent(person -> System.out.println(person.getName())); // 출력: Alice