개요
자바 Stream API는 자바 8에서 도입된 기능으로, 컬렉션 데이터에 대해 함수형 프로그래밍 스타일로 데이터를 처리할 수 있게 해준다.
Stream은 데이터의 흐름을 의미하며, 컬렉션이나 배열에 저장된 데이터를 일련의 연산을 통해 처리할 수 있도록 돕는다.
Stream API는 컬렉션이나 배열과 같은 데이터 소스를 처리할 수 있으며, 주로 배열, 컬렉션, 입출력 스트림 등을 사용하여 선언적 방식으로 데이터 처리 로직을 작성할 수 있다.
주요 특징은 람다식과 함께 사용되어 가독성을 높이고, 병렬 처리를 손쉽게 할 수 있다는 점이다.
Stream API는 컬렉션의 데이터를 쉽게 변환하고 처리하는 데 매우 유용하다.
예를 들어, 리스트의 중복을 제거하거나, 객체의 특정 필드만 뽑아서 새로운 리스트로 만들거나, 조건에 맞는 값만 뽑아내는 등의 작업을 선언적으로 처리할 수 있다.
Stream의 기본 특징
- 선언적: 코드의 의도를 명확히 하며, 선언적인 방식으로 데이터를 처리한다.
- 불변성: Stream은 원본 데이터를 변경하지 않고 변환된 결과를 새 Stream으로 반환한다.
- 지연 평가(Lazy Evaluation): Stream의 연산은 결과가 실제로 필요할 때까지 실행되지 않는다.
- 파이프라인(Pipeline): 연산은 중간 연산과 최종 연산으로 나뉘며, 파이프라인을 통해 연속적으로 처리된다.
중간 연산 메서드
중간 연산은 Stream을 변환하거나 필터링하는 연산으로, 여러 중간 연산을 파이프라인으로 연결할 수 있다.
중간 연산은 지연 평가되므로 최종 연산이 호출될 때까지 실행되지 않는다.
- filter(): 조건에 맞는 요소를 필터링한다.
- map(): 각 요소를 변환한다.
- flatMap(): 각 요소를 다른 컬렉션으로 펼친다.
- distinct(): 중복을 제거한다.
- sorted(): 요소를 정렬한다.
- peek(): Stream을 소비하지 않고, 각 요소를 출력할 수 있다.
최종 연산 메서드
최종 연산은 Stream을 소비하여 결과를 반환하는 연산이다.
최종 연산이 호출되면 Stream은 더 이상 사용할 수 없다.
- forEach(): 각 요소에 대해 작업을 수행한다.
- collect(): 요소들을 수집하여 다른 형태로 변환한다.
- reduce(): 요소들을 하나로 합친다.
- count(): 요소의 개수를 반환한다.
- min() / max(): 최소값과 최대값을 반환한다.
- anyMatch(), allMatch(), noneMatch(): 조건을 만족하는 요소가 있는지 확인한다.
- findFirst(), findAny(): 첫 번째 요소나 아무거나 반환한다.
filter()
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 짝수만 필터링
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0) // 필터링
.collect(Collectors.toList()); // 결과를 리스트로 수집
System.out.println(evenNumbers); // [2, 4, 6]
}
}
- Interger객체 타입 리스트 numbers에서 filter를 통해 짝수만 필터링 하였다.
- 이후 collect를 통해 결과를 리스트로 수집하여 evenNumbers 리스트에 파싱하였다.
- evenNumbers의 요소를 출력하면 짝수만 존재함을 알 수 있다.
map()
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 각 이름을 대문자로 변환
List<String> upperNames = names.stream()
.map(String::toUpperCase) // 대문자로 변환
.collect(Collectors.toList()); // 결과를 리스트로 수집
System.out.println(upperNames); // [ALICE, BOB, CHARLIE]
}
}
- String객체 타입 리스트 names에서 각 요소에 map을 통해 String클래스의 함수 toUpperCase를 적용해 주었다.
- 이후 collect를 통해 결과를 리스트로 수집하여 upperNames 리스트에 파싱하였다.
- upperNames의 요소를 출력하면 모든 문자열이 대문자로 바뀐 것을 볼 수 있다.
reduce()
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 합산
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b); // 초기값 0부터 시작하여 각 값을 더함
System.out.println(sum); // 15
}
}
- Integer객체 타입 리스트 numbers에서 reduce를 통해 초기값 0부터 각 요소를 더해주었다.
- sum을 출력할 경우 각 요소의 값 15가 출력됨을 알 수 있다.
복합적인 Stream 처리
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Paul", "George", "Ringo");
// 이름 길이가 4 이상인 이름만 대문자로 변환 후 정렬
List<String> result = names.stream()
.filter(name -> name.length() >= 4)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(result); // [GEORGE, RINGO]
}
}
- String객체 타입 리스트 names를 초기화 한다.
- filter를 통해 이름의 길이가 5이상인 요소만 필터링 한다.
- map을 통해 해당 문자열들을 String클래스의 toUpperCase메서드를 활용해 대문자로 변경해 준다.
- sorted를 통해 각 요소들을 오름차순으로 정렬해 준다.
- collect를 통해 리스트로 변경 후 String객체 타입의 리스트 result에 저장해 준다.
- result를 출력 시 이름이 길이가 5이상인 이름이 대문자로 출력됨을 알 수 있다.
병렬 스트림
Stream API는 병렬 처리를 쉽게 할 수 있도록 도와준다.
parallelStream()을 사용하면 데이터가 자동으로 여러 스레드에서 처리된다.
병렬 스트림은 멀티코어 CPU에서 성능을 극대화할 수 있다.
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 병렬 스트림으로 처리
int sum = numbers.parallelStream()
.reduce(0, Integer::sum); // 합산
System.out.println(sum); // 21
}
}
분할 (Partitioning)
분할은 데이터를 두 그룹으로 나누는 작업으로, 주로 boolean 조건을 사용한다.
Collectors.partitioningBy()는 주어진 조건을 만족하는 요소를 하나의 그룹으로, 그렇지 않은 요소를 다른 그룹으로 나누어 Map<Boolean, List<T>> 형태로 반환한다.
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 짝수와 홀수로 분할
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitioned); // {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
}
}
- partitioningBy()는 true와 false의 두 그룹으로 나누어 Map<Boolean, List<T>> 형태로 결과를 반환한다.
- 위 예제에서는 짝수를 true 그룹에, 홀수를 false 그룹에 배치한다.
그루핑 (Grouping)
그루핑은 데이터를 특정 기준에 따라 그룹화하는 작업이다.
Collectors.groupingBy()는 조건에 맞는 그룹을 Map 키-값 쌍으로 만들며, 반환형은 Map<K, List<T>>이다.
그룹화 기준은 주로 Function<T, K>를 사용해 정할 수 있다.
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "avocado", "blueberry", "cherry", "Bam");
// 첫 글자로 그룹화
Map<Character, List<String>> grouped = words.stream()
.collect(Collectors.groupingBy(word -> word.charAt(0)));
System.out.println(grouped); // {a=[apple, avocado], B=[Bam], b=[banana, blueberry], c=[cherry]}
}
}
- groupingBy()는 각 요소를 그룹화 기준에 따라 나누고, Map<K, List<T>> 형식으로 반환한다.
- 위 예제에서는 단어의 첫 글자를 기준으로 그룹화했다.
- charAt메서드는 대소문자를 구분한다.
'웹(WEB) > 자바(Java)' 카테고리의 다른 글
[Java] 자바 람다 Lambda (0) | 2024.12.31 |
---|---|
[Java] 자바 자료구조, Collection (0) | 2024.12.31 |
[Java] 자바 annotation (0) | 2024.12.31 |
[Java] 자바 Enum (0) | 2024.12.31 |
[Java] 자바 Generics (0) | 2024.12.30 |