개념

  • Java 8부터 추가된 기능으로 컬렉션, 배열과 같은 다양한 데이터 소스를 추상화하여 일련의 계산을 표현한다.
  • 스트림을 사용하여 데이터를 필터링, 매핑, 집계하는 등의 작업을 더 간결하고 선언적으로 표현할 수 있다.

특징

  • 선언전 방식 : 처리하는 방식보다 처리할 데이터에 집중할 수 있다.
  • 파이프라이닝 : 체이닝을 통해 연결할 수 있고, 중간 연산과 최종 연산으로 나뉜다.
  • 내부 반복 : 내부적으로 반복을 처리하여 더 나을 성능과 코드 가독성을 제공한다.
  • 불변성 : 원본 데이터를 변경하지 않고, 결과만을 반환한다.
  • 지연 평가 : 중간 연산은 지연평가를 수행하여 실제로 필요한 시점에만 계산을 수행한다.

구성

  • 스트림 연산은 생성 -> 중간 연산 -> 최종 연산 순으로 구성된다.

1. 생성

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
  • stream() 메서드를 통해 스트림을 생성한다.

2. 중간 연산

List<String> result = list.stream()
    .filter(s -> s.startsWith("a"))  // 필터링
    .map(String::toUpperCase)        // 매핑
    .sorted()                        // 정렬
    .toList();                       // 최종 연산이 호출될 때까지 실제로 실행되지 않음
  • 중간 연산은 스트림을 변환하지만 최종결과를 반환하지 않는다.
  • 이러한 연산은 지연 평가된다.
  • 중간 연산 메서드
    • filter(Predicate predicate): 조건에 맞는 요소만 필터링한다. if 문 역할을 한다.
    • map(Function<T, R> mapper): 각 요소를 특정 값으로 변환한다. 람다를 인자로 받아. 스트림을 원하는 모양의 새로운 스트림으로 변환할 때 사용한다.
    • flatMap(Function<T, Stream> mapper): 중첩된 스트림을 평면화한다.
    • sorted(): 스트램 내 요소들을 정렬한다. 정렬 조건으로 Comparator를 사용한다.
    • distinct(): 스트림 내 중복을 제거한다.
    • limit(long maxSize): 스트림의 크기를 제한한다.
    • skip(long n): 처음 n개의 요소 건너뛴다.

3. 최종 연산

  • 스트림은 최종 연산을 통해 데이터를 처리하고, 결과를 도출한다.

① 계산(Calculaing) : 기본 형 타입을 사용하는 경우 스트림 내 요소들로 최소, 최대, 합, 평균 등을 계산하여 결과를 도출할 수 있다.

IntStream stream = list.stream();
// 스트림 요소 개수 반환
long count = stream.count();
// 스트림 요소의 합 반환
int sum = stream.sum();
// 스트림의 최소값 반환
OptionalInt min = stream.min();
// 스트림의 최대값 반환
OptionalInt max = stream.max();
// 스트림의 평균값 반환
OptionalDouble average = stream.average();

② 축소 (Reduction) : 스트림의 요소를 하나씩 줄여가며 누적 연산을 수행한다.

IntStream stream = IntStream.range(1, 5);
int result = stream.reduce(10, (total, num) -> total + num);
// 10 + 1 + 2 + 3 + 4 = 20

③ 수집 (Collecting) : 스트림의 요소를 원하는 자료형으로 변환한다.

// toList() - 리스트로 반환
List<String> lastNames = members.stream()
    .map(Person::getLastName)
    .collect(Collectors.toList());

// joining() - 작업 결과를 하나의 스트링으로 이어 붙이기
String names = members.stream()
    .map(Person::getLastName)
    .collect(Collectors.joining("+", "<", ">"));

// groupingBy() - 그룹 지어서 Map으로 반환
Map<Integer, List<Person>> groupedByAge = members.stream()
    .collect(Collectors.groupingBy(Person::getAge));

// collectingAndThen() - 수집한 결과를 변환
Set<Person> unmodifiableSet = members.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toSet(),
        Collections::unmodifiableSet
    ));

④ 매칭 (Matching) : 특정 조건을 만족하는 요소가 있는 체크한다. 만족하면 true, 만족하지 않으면 false를 반환한다.

List<String> members = Arrays.asList("Lee", "Park", "Hwang");

// 하나라도 만족하는 요소가 있는지
boolean anyMatch = members.stream()
    .anyMatch(member -> member.contains("w")); // true

// 모든 요소가 조건을 만족하는지
boolean allMatch = members.stream()
    .allMatch(member -> member.length() >= 4); // false

// 모든 요소가 조건을 만족하지 않는지
boolean noneMatch = members.stream()
    .noneMatch(member -> member.endsWith("t")); // true

⑤ 반복 (Iterating) : 스트림의 각 요소를 돌며 인수에 대한 작업을 수행한다.

members.stream()
    .map(Person::getName)
    .forEach(System.out::println);

⑥ 찾기 (Finding) : 스트림에서 하나의 요소만을 찾아 반환한다.

Person person1 = members.stream()
    .findAny(); // 병렬 스트림의 경우 첫 번째 요소가 보장되지 않음

Person person2 = members.stream()
    .findFirst(); // 첫 번째 요소 반환