01. 스트림의 중간 연산
1. 중간 연산 (Intermediate Operation)
스트림의 중간 연산이란 스트림 생성 이후에 수행되는 연산으로, 연산 결과로 스트림을 반환하기 때문에 여러 개의 연산자를 연결하여 우리가 원하는 데이처 처리를 수행할 수 있다. 즉, 중간 연산은 데이터를 우리가 원하는 형태로 처리 및 가공하는 역할을 수행한다.

2. 주요 중간 연산자 종류
| 주요 중간 연산자 | 메서드 | 매개 값 | |
| 필터링 | filter() | 조건에 맞는 데이터들만을 정제하여 더 작은 컬렉션을 생성 | 조건 (람다식을 통해 정의) |
| 매핑 | map() | 원하는 필드만 추출하거나 특정 형태로 변환 | 조건 (람다식을 통해 정의) |
| 중복 제거 | distinct() | 스트림 내 중복 요소를 제거 | X |
| 정렬 | sorted() | 스트림 요소를 정렬 >> 기본 정렬 또는 사용자 정의 정렬 제공 |
사용자 정의 정렬 >> 별도의 comparator |
| 평탄화 | flatMap() | 중첩된 스트립 구조를 하나의 스트림으로 펼침 | |
| 엿보기 | peek() | 스트림의 각 요소를 중간에 엿보고 처리 >> 디버깅 또는 로깅을 목적으로 수행 >> side-effect 없이 사용하며, 데이터 조작 용도로 쓰면 안 됨 |
|
| 건너뛰기 | skip() | 앞의 n개의 요소를 건너뜀 | 건너뛸 요소의 개수 |
| 제한 | limit() | 앞에서부터 최대 n개의 요소만 남김 | 남길 요소의 최대 개수 |
// 1. 필터링 예제
import java.util.Arrays;
import java.util.List;
public class FilteringExample {
public static void main(String[] args) throws Exception {
List<String> names = Arrays.asList("김코딩", "이자바", "박해커", "김코딩", "박해커");
names.stream()
.filter(element -> element.charAt(0) == '김') // 김씨 성을 가진 요소만 필터링
.forEach(element -> System.out.println(element));
System.out.println();
}
}
// 2. 매핑 예제
import java.util.Arrays;
import java.util.List;
public class IntermediateOperationExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("kimcoding", "javalee", "hackerna", "luckyguy");
names.stream()
.map(element -> element.toUpperCase()) // 요소들을 하나씩 대문자로 변환
.forEach(element->System.out.println(element));
}
}
// 2-1. 매핑 예제 2
import java.util.Arrays;
import java.util.List;
public class IntermediateOperationExample {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 6, 9);
// 각 요소에 3을 곱한 값을 반환
list.stream().map(number -> number * 3).forEach(System.out::println);
}
}
// 3. 중복 예제
List<String> list = Arrays.asList("java", "sql", "mybatis", "jdbc", "spring", "java", "mybatis");
list.stream()
.distinct()
.forEach(System.out::println);
// 4. 정렬 예제
List<String> list = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
list.stream().sorted().forEach(System.out::println); // 기본 오름차순
list.stream() // 사용자 정의 정렬 (내림차순)
.sorted((s1, s2) -> s2.compareTo(s1))
.forEach(System.out::println);
// 5. 평탄화 예제
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("JAVA", "SPRING"),
Arrays.asList("java", "spring")
);
nestedList.stream()
.flatMap(Collection::stream)
.forEach(System.out::println);
// 6. 엿보기 예제
List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");
names.stream()
.filter(name -> name.length() > 3)
.peek(name -> System.out.println("필터링 통과: " + name))
.map(String::toUpperCase)
.forEach(System.out::println);
// 7. 건너뛰기 예제
IntStream.rangeClosed(1, 10)
.skip(5)
.forEach(System.out::println); // 6~10 출력
// 8. 제한 예제
IntStream.rangeClosed(1, 10)
.limit(5)
.forEach(System.out::println); // 1~5 출력
02. 스트림의 최종 연산
스트림의 최종 연산이란, 스트림 파이프라인 마지막 단계에서 단 한 번 수행되는 연산을 의미한다. 앞서 언급한 것처럼 최종 연산이 끝난 스트림은 재사용이 불가능하며, 주로 결과를 반환하거나 데이터를 소비한다.
| 주요 최종 연산자 | ||
| 순회 | forEach() | 스트림의 각 요소를 순회하면서 지정된 작업을 수행 >> 내부적으로 Consumer<T> 함수형 인터페이스를 사용 >> 중간 연산자와 달리 데이터를 가공하지 않고 소비 |
| 수집 | Collect() | 스트림 결과를 List, Set. Map 등의 컬렉션으로 수집 >> Collector 유틸리티 클래스의 정적 메서드를 활용 |
| 조건 매칭 | allMatch() / anyMatch() / noneMatch() | 스트릠의 요소들이 조건을 만족하는지 boolean 값으로 검사 |
| 단일 요소 반환 | findFirst() / findAny() | 스트림에서 하나의 요소를 Optional로 반환 >> findAny()는 병렬 처리 시 빠른 요소 반환 |
| 배열 반환 | toArray() | 스트림을 배열로 반환 |
// 1. 순회 예제
import java.util.List;
public class ForEachExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.forEach(name -> System.out.println("이름: " + name));
}
}
// 2. 수집 예제
List<String> list = Stream.of("A", "B", "C")
.collect(Collectors.toList());
// 2-1. toMap() 예제
List<Student> totalList = Arrays.asList(
new Student("김코딩", 100, Student.Gender.Male),
new Student("박해커", 80, Student.Gender.Male),
new Student("이자바", 90, Student.Gender.Female),
new Student("나미녀", 60, Student.Gender.Female)
);
Map<String, Integer> maleMap = totalList.stream()
.filter(s -> s.getGender() == Student.Gender.Male)
.collect(Collectors.toMap(Student::getName, Student::getScore));
System.out.println(maleMap); // {김코딩=100, 박해커=80}
// 2-2. joining() 사용
List<Member> members = List.of(
new Member("m1", "홍길동"),
new Member("m2", "이몽룡")
);
String result = members.stream()
.map(Member::getMemberName)
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(result);
// 3. 조건 매칭 예제
int[] nums = {2, 4, 6};
System.out.println(Arrays.stream(nums).allMatch(n -> n % 2 == 0)); // true
System.out.println(Arrays.stream(nums).anyMatch(n -> n % 3 == 0)); // true
System.out.println(Arrays.stream(nums).noneMatch(n -> n < 0)); // true
// 4. 단일 요소 반환 예제
Optional<String> first = Stream.of("a", "b", "c").findFirst();
Optional<String> any = Stream.of("a", "b", "c").findAny();
// 5. 배열 변환 예제
int[] arr = IntStream.rangeClosed(1, 5).toArray();
System.out.println(Arrays.toString(arr));
1. 지연 평가 (Lazy Evaluation)
지연 평가란 스트림 중간 연산은 최종 연산이 호출되기 전까지 실제로 실행되지 않음을 의미한다. 즉, 각 요소는 파이프라인을 통과하면서 최종 연산까지 한 번에 처리됨을 의미한다. 이를 통해 불필요한 연산을 피할 수 있을 뿐만 아니라 성능 최적화가 가능해진다.
public class LazyEvaluationExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie", "Anna");
// 스트림 생성시 중간 연산에서 출력은 발생하지 않음
var stream = names.stream()
.filter(name -> {
System.out.println("Filtering: " + name);
return name.startsWith("A");
});
// 최종 연산이 호출되어야 중간 연산이 실행됨
System.out.println("최종 연산 시작");
// 이 예제에서는 가장 대표적인 forEach()를 최종 연산으로 사용
stream.forEach(System.out::println);
}
}
03. 익명 클래스를 활용한 기본 연산
1. filter()와 Predicate<T>
// 1. 익명 클래스 사용
public class FilterExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String name) {
return name.startsWith("A");
}
})
.forEach(System.out::println);
}
}
// 2. 람다 표현식 사용
public class FilterExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
}
}
2. map()과 Function<T, R>
// 1. 익명 클래스 사용
public class MapExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.map(new Function<String, Integer>() {
@Override
public Integer apply(String name) {
return name.length();
}
})
.forEach(System.out::println);
}
}
// 2. 람다 표현식 사용
public class MapExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.map(name -> name.length())
.forEach(System.out::println);
}
}
3. forEach()와 Consumer<T>
// 1. 익명 클래스 사용
public class ForEachExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println("이름: " + name);
}
});
}
}
// 2. 람다 표현식 사용
public class ForEachExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Anna");
names.stream()
.forEach(name -> System.out.println("이름: " + name));
}
}
'[Codeit Review] > JCF와 Stream API' 카테고리의 다른 글
| [05] Stream 추가 연산 (0) | 2026.01.13 |
|---|---|
| [03] Stream API의 개념과 구조 (0) | 2026.01.13 |
| [02] 람다 표현식 (Lambda Expression) (0) | 2026.01.13 |
| [01] JCF (Java Collection Framework) (0) | 2026.01.13 |