728x90
반응형
목차
1. 스트림(Stream) API란? 주요 개념과 특징
스트림 API는 자바에서 컬렉션, 배열, 또는 I/O 자원의 데이터를 선언적이고 함수형으로 처리할 수 있게 해 줍니다. 기존의 반복문 대신 체이닝 방식으로 데이터를 변환, 필터링, 집계할 수 있습니다.
주요 개념
- 스트림 (Stream): 데이터의 흐름. 컬렉션, 배열 등 다양한 데이터 소스로부터 생성될 수 있으며, 일회성으로 사용됩니다. (한번 사용하면 닫힘)
- 중간 연산 (Intermediate Operation): 스트림을 변환하는 연산. 여러 개의 중간 연산을 연결하여 파이프라인을 구성할 수 있습니다. (e.g., filter, map, sorted)
- 최종 연산 (Terminal Operation): 스트림 파이프라인을 실행하고 결과를 생성하는 연산. 스트림은 최종 연산이 실행될 때 비로소 데이터를 처리합니다. (e.g., forEach, collect, reduce)
- 람다 표현식 (Lambda Expression): 익명 함수를 간결하게 표현하는 방법. 스트림 API에서 중간 연산 및 최종 연산에 사용됩니다. (e.g., x -> x > 5)
- 파이프라인(Pipeline): 스트림 연산을 연결해 처리하는 구조.
특징
- 선언형 프로그래밍: 코드가 간결하고 가독성이 높아집니다. "어떻게"가 아닌 "무엇을" 처리할지 정의합니다.
- 파이프라인: 여러 연산을 연결하여 데이터 처리 과정을 명확하게 표현할 수 있습니다.
- 지연 연산 (Lazy Evaluation): 최종 연산이 호출될 때까지 중간 연산이 실행되지 않아 효율적인 처리가 가능합니다.
- 병렬 처리: 멀티코어 환경에서 데이터를 병렬로 처리하여 성능을 향상시킬 수 있습니다. parallelStream()으로 멀티스레드 활용 가능합니다.
- 불변성: 원본 데이터를 수정하지 않습니다.
2. 스트림 API 예시와 코드 설명
예제 1: 숫자 리스트에서 짝수만 골라 제곱한 후 합계 구하기
import java.util.Arrays;
import java.util.List;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 스트림 API를 사용하여 짝수만 골라 제곱한 후 합계 구하기
int sum = numbers.stream()
.filter(number -> number % 2 == 0) // 짝수만 필터링
.map(number -> number * number) // 제곱
.reduce(0, Integer::sum); // 합계 (초기값 0, Integer.sum 메서드 사용)
System.out.println("짝수 제곱의 합: " + sum); // 출력: 짝수 제곱의 합: 220
}
}
- numbers.stream(): numbers 리스트를 스트림으로 변환합니다.
- .filter(number -> number % 2 == 0): 람다 표현식을 사용하여 각 숫자가 짝수인지 판별하고, 짝수만 통과시킵니다.
- .map(number -> number * number): 람다 표현식을 사용하여 각 숫자를 제곱합니다.
- .reduce(0, Integer::sum): 스트림의 모든 숫자를 더합니다. 0은 초기값이며, Integer::sum은 두 숫자를 더하는 메서드 레퍼런스입니다.
예제 2: 문자열 리스트에서 특정 길이 이상의 문자열만 추출하여 대문자로 변환 후 정렬하기
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample2 {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "kiwi", "orange", "grape");
// 스트림 API를 사용하여 특정 길이 이상의 문자열만 추출하여 대문자로 변환 후 정렬하기
List<String> result = words.stream()
.filter(word -> word.length() > 4) // 길이가 4보다 큰 문자열만 필터링
.map(String::toUpperCase) // 대문자로 변환
.sorted() // 정렬
.collect(Collectors.toList()); // 결과를 List로 수집
System.out.println("결과: " + result); // 출력: 결과: [APPLE, BANANA, GRAPE, ORANGE]
}
}
- words.stream(): words 리스트를 스트림으로 변환합니다.
- .filter(word -> word.length() > 4): 람다 표현식을 사용하여 각 문자열의 길이가 4보다 큰지 판별하고, 길이가 4보다 큰 문자열만 통과시킵니다.
- .map(String::toUpperCase): 메서드 레퍼런스를 사용하여 각 문자열을 대문자로 변환합니다.
- .sorted(): 문자열을 사전순으로 정렬합니다.
- .collect(Collectors.toList()): 스트림의 결과를 List로 수집합니다.
예제 3: 리스트에서 짝수만 필터링 후 제곱 계산
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> result = numbers.stream() // 스트림 생성
.filter(n -> n % 2 == 0) // 짝수 필터링
.map(n -> n * n) // 제곱 계산
.collect(Collectors.toList()); // 리스트로 수집
System.out.println(result); // 출력: [4, 16, 36]
}
}
📌 주요 연산 분석:
- numbers.stream(): List<Integer>에서 스트림 생성.
- 데이터 소스를 스트림으로 변환, 이후 연산 준비.
- .filter(n -> n % 2 == 0): 중간 연산, 짝수만 남김.
- 람다 표현식으로 조건 정의, true인 요소만 통과.
- 결과: [2, 4, 6].
- .map(n -> n * n): 중간 연산, 각 요소를 제곱.
- 입력값을 변환, 새로운 스트림 생성.
- 결과: [4, 16, 36].
- .collect(Collectors.toList()): 최종 연산, 결과를 리스트로 수집.
- 스트림을 종료하며 결과를 반환.
📌 단계별 동작:
- 입력: [1, 2, 3, 4, 5, 6].
- 필터링 후: [2, 4, 6].
- 맵핑 후: [4, 16, 36].
- 수집: List<Integer>로 저장.
📌 특징:
- 코드가 간결하고 가독성 높음.
- 원본 리스트(numbers)는 변경되지 않음.
3. 실무 코딩에서 스트림 API 적용 예시
스트림 API는 데이터 처리, 비즈니스 로직 간소화, 집계 작업에서 실무적으로 유용합니다. Spring Boot에서의 예시를 보겠습니다.
예시: 사용자 목록 필터링 및 변환
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserService {
private final List<User> users = List.of(
new User(1L, "Alice", 25),
new User(2L, "Bob", 30),
new User(3L, "Charlie", 22)
);
public List<String> getAdultUserNames() {
return users.stream() // 스트림 생성
.filter(user -> user.getAge() >= 25) // 25세 이상 필터링
.map(User::getName) // 이름만 추출 (메서드 참조)
.sorted() // 알파벳순 정렬
.collect(Collectors.toList()); // 리스트로 수집
}
static class User {
private Long id;
private String name;
private int age;
public User(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public static void main(String[] args) {
UserService service = new UserService();
System.out.println(service.getAdultUserNames()); // 출력: [Alice, Bob]
}
}
📌 주요 연산 분석:
- users.stream(): 사용자 리스트에서 스트림 생성.
- .filter(user -> user.getAge() >= 25): 25세 이상 사용자만 남김.
- 결과: [Alice(25), Bob(30)].
- .map(User::getName): User 객체에서 이름만 추출.
- 메서드 참조(::)로 간결하게 작성.
- 결과: ["Alice", "Bob"].
- .sorted(): 이름 기준 오름차순 정렬.
- 결과: ["Alice", "Bob"] (이미 정렬됨).
- .collect(Collectors.toList()): 결과를 리스트로 반환.
📌 실무 활용:
- Spring 컨트롤러에서 호출해 뷰에 데이터 제공 가능.
- DB에서 조회한 데이터를 가공하는 데 유용.
4. 스트림 API의 주의사항: 장점과 단점
장점
- 코드 간결성 및 가독성 향상: 복잡한 반복문 코드를 줄이고, 데이터 처리 과정을 명확하게 표현할 수 있습니다.
- 생산성 향상: 개발 시간을 단축하고 유지보수를 용이하게 합니다.
- 병렬 처리 지원: 멀티코어 환경에서 성능을 향상시킬 수 있습니다.
- 지연 연산: 불필요한 연산을 줄여 효율적인 처리가 가능합니다.
단점
- 성능 오버헤드: 데이터 양이 적은 경우, 스트림 생성 및 연산 과정에서 성능 오버헤드가 발생할 수 있습니다.
- 디버깅 어려움: 파이프라인 구조로 인해 중간 결과를 확인하기 어렵고, 디버깅이 복잡해질 수 있습니다.
- 가독성 저하 가능성: 람다 표현식이 복잡해지면 오히려 가독성을 저해할 수 있습니다.
- 사이드 이펙트 주의: 스트림 연산 내에서 외부 변수를 변경하는 등의 사이드 이펙트를 발생시키지 않도록 주의해야 합니다.
주의사항
- 불필요한 스트림 사용 자제: 간단한 작업은 전통적 반복문이 더 효율적일 수 있음.
- 상태 변경 피하기: 스트림 내에서 외부 변수 수정은 부작용(side effect)을 유발.
- 메모리 사용: 대규모 데이터에서 parallelStream() 사용 시 메모리 부하 주의.
5. 결론
스트림 API는 자바 개발자가 컬렉션 데이터를 효율적으로 처리할 수 있도록 돕는 강력한 도구입니다. 하지만 모든 상황에 적합한 것은 아니며, 데이터 양, 코드 복잡도, 성능 요구 사항 등을 고려하여 적절하게 사용해야 합니다.
✨ 스트림 API의 장점과 단점을 명확히 이해하고, 상황에 맞게 활용한다면 여러분의 자바 코딩 실력은 더욱 향상될 것입니다. ✨
728x90
반응형
'이직&취업 > Java 기초 상식' 카테고리의 다른 글
ArrayList와 LinkedList의 차이점은 무엇인가요? (29) | 2025.04.08 |
---|---|
자바에서 static 키워드는 어떤 역할을 하나요? (40) | 2025.04.08 |
자바에서 NullPointerException은 왜 발생하고 어떻게 해결하나요? (26) | 2025.04.06 |
JAVA 직렬화(Serialization) 란 무엇인가? (14) | 2025.03.29 |
JAVA final / finally / finalize 알아보자!! (18) | 2025.03.28 |