스트림 (Stream)
자바 스트림(Stream)은 Java 8에서 도입된 기능으로, 컬렉션이나 배열 등의 데이터를 함수형 방식으로 처리할 수 있게 해주는 핵심 기능이다. '데이터 소스'에서 추출된 요소들이 순차적으로 흐르는 파이프라인을 의미한다. 코드를 간결하고 가독성 있게 만들어주며 병렬 처리까지 쉽게 해준다.
특징
1) 데이터 소스 : 배열, 컬렉션 등에서 얻는다.
2) 함수형 : 익명 함수 (람다)를 사용하여 코드를 간결하게 만든다.
3) 비파괴적 : 원본 데이터를 변경하지 않는다.
4) 단 한 번만 사용 : 스트림은 최종 연산 후 닫히며 재사용할 수 없다.
왜 스트림을 사용하는가 ?
- 전통적인 방식 : 외부 반복으로 개발자가 직접 인덱스를 관리하고 반복을 제어해야 한다.
- 스트림 방식 : 내부 반복으로 데이터 처리에 대한 로직만 제공하면, 스트림이 알아서 반복을 처리하고 최적화한다.
스트림 파이프라인의 3단계 작동 원리
스트림을 이용한 데이터 처리는 항상 생성 -> 중간 연산 -> 최종 연산의 3 단계를 거치는 파이프라인 구조를 가진다.
1) 1단계 : 스트림 생성
데이터 소스를 스트림으로 만든다.
| 메서드 | 설명 | 예시 |
| Collection.stream() | List, Set 등 컬렉션에서 스트림 생성 | list.stream() |
| Arrays.stream() | 배열에서 스트림 생성 | Arrays.sream(arr) |
| Stream.of() | 고정된 값(리터럴)들로 스트림 생성 | Stream.of("A", "B", "C") |
| IntStream.range() | 기본 타입 범위 스트림 생성 | IntStream.range(1,10) |
2) 2단계 : 중간 연산
데이터를 필터링, 가공, 변형하는 단계이다. 중간 연산은 여러 개를 연결할 수 있으며, 연산 결과로 다시 스트림을 반환한다.
| 메서드 | 역할 | 설명 |
| filter(Predicate) | 조건에 맞는 요소만 걸러냄 | filter(n -> n > 10) |
| map(Function) | 요소를 원하는 형태로 변환 | map(s -> s.length()) |
| sorted() | 요소들을 정렬 | sorted() 또는 sorted(Comparator) |
| distinct() | 중복 요소 제거 | |
| limit(n) | 앞에서부터 n개만 잘라냄 |
중간 연산은 실제 데이터를 처리하지 않는다. 최종 연산이 호출될 때까지 대기한다.
3) 3단계 : 최종 연산
구축된 파이프라인을 실행시키고, 결과를 반환하거나 스트림을 소비한다. 이 연산이 호출되는 순간에만 중간 연산들이 모두 실행된다.
| 메서드 | 역할 | 반환 타입 |
| forEach(Consumer) | 각 요소를 반복하여 작업 수행 | void |
| collect(Collector) | 스트림의 요소를 다시 컬렉션으로 모음 | List, Set, Map 등 |
| reduce() | 모든 요소를 하나로 합쳐서 반환 | Optional 또는 타입 |
| count() | 요소의 개수 반환 | long |
| anyMatch() | 조건에 맞는 요소가 하나라도 있는지 확인 | boolean |
예시
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Employee {
private String name;
private int salary;
private String department;
public Employee(String name, int salary, String department) {
this.name = name;
this.salary = salary;
this.department = department;
}
// Getter 메서드
public String getName() { return name; }
public int getSalary() { return salary; }
public String getDepartment() { return department; }
@Override
public String toString() {
return "Employee{" + "name='" + name + '\'' + ", salary=" + salary + ", dept='" + department + '\'' + '}';
}
}
public class StreamUsageExample {
public static void main(String[] args) {
// 데이터 소스 생성
List<Employee> allEmployees = Arrays.asList(
new Employee("김철수", 6000, "개발부"),
new Employee("이영희", 4500, "영업부"),
new Employee("박민수", 7500, "개발부"),
new Employee("최지영", 4000, "마케팅부"),
new Employee("김하나", 5500, "영업부")
);
// 1. 필터링 (filtering)
// 급여가 5000만 원 이상인 직원만 추출
List<Employee> highPaidList = allEmployees.stream()
.filter(emp -> emp.getSalary() >= 5000)
.collect(Collectors.toList());
System.out.println("고액 연봉자: " + highPaidList);
// 결과: [Employee{name='김철수', salary=6000, ...}, Employee{name='박민수', salary=7500, ...}, Employee{name='김하나', salary=5500, ...}]
// 2. 데이터 가공 및 변환
// 직원 목록에서 이름만 추출
List<String> names = allEmployees.stream()
.map(Employee::getName) // Employee -> String 변환
.collect(Collectors.toList());
System.out.println("직원 이름 목록: " + names);
// 결과: [김철수, 이영희, 박민수, 최지영, 김하나]
// 3. 데이터 집계
// 영업부 직원들의 급여 총합 계산
long salesTotalSalary = allEmployees.stream()
.filter(emp -> "영업부".equals(emp.getDepartment())) // 1. 영업부 필터링
.mapToLong(Employee::getSalary) // 2. 급여를 long 스트림으로 변환
.sum(); // 3. 최종 연산: 합계
System.out.println("영업부 급여 총합: " + salesTotalSalary + "만 원");
// 결과: 4500 + 5500 = 10000만 원
// 4. 조건 검사
// '마케팅부'에 속한 직원이 한 명이라도 있는지 확인
boolean isMarketingPresent = allEmployees.stream()
.anyMatch(emp -> "마케팅부".equals(emp.getDepartment()));
System.out.println("마케팅부 존재 여부: " + isMarketingPresent);
// 결과: true (찾는 즉시 스트림 종료)
// 5. 데이터 그룹화
// 부서별로 직원 목록을 그룹화
Map<String, List<Employee>> employeesByDept = allEmployees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println("부서별 그룹: " + employeesByDept);
// 결과: {개발부=[김철수, 박민수], 영업부=[이영희, 김하나], 마케팅부=[최지영]}
}
}'Java' 카테고리의 다른 글
| [Java] 자바 변수 4가지 정리 (전역, 지역, 정적, 멤버 변수) (0) | 2025.11.10 |
|---|---|
| [Java] 원시타입(Primitive Type)과 참조타입(Reference Type) (0) | 2025.10.14 |
| [Java] 실수를 나타내는 Double, double, float, decimal 의 차이 (0) | 2025.10.13 |
| [Java] POJO(Plain Old Java Object) (0) | 2025.09.16 |
| [Java] 객체지향설계의 5가지 원칙 (SOLID 원칙) (2) | 2025.08.03 |
