46. 스트림에서는 부작용 없는 함수를 사용하라
📌 1. 발표 전 알아야 할 개념
스트림(Stream)
47장의 Stream과 의미의 차이를 이해해보자.
자바 8에서 도입된 기능으로, 리스트, 배열과 같은 데이터 컬렉션을 함수형 스타일로 처리하는 방식
반복문 대신 더 간결하게 데이터를 처리할 수 있게 됨
public class StreamTest {
public static void main(String[] args) {
// 짝수를 찾고 제곱하기
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> result = new ArrayList<>();
// 전통적인 방식
for(Integer n : numbers){
if(n % 2==0) {
result.add(n*n);
}
}
System.out.println(result);
// stream 방식
List<Integer> result2 = numbers.stream()
.filter(n -> n%2==0)
.map(n->n*n)
.collect(Collectors.toList());
System.out.println(result2);
}
}stream(): 데이터 흐름을 시작filter(): 조건에 맞는 데이터만 고르기map(): 데이터를 가공해서 변환collect(): 결과 수집
📕 2. 부작용
부작용(Side Effect)
함수가 자신의 범위를 벗어나 외부 상태를 변경하는 것
☝🏼 3. 스트림에서는 부작용 없는 함수를 사용하라라
3-1. 순수함수(Pure Function)
같은 입력에는 항상 같은 출력을 반환
함수 외부의 상태를 변경하지 않는다 -> 부작용이 없다
3-2. for-each VS Stream
for-each 반복
명령형 프로그래밍 스타일로, 어떻게(How) 처리할지 집중함
외부 변수를 쉽게 수정할 수 있음
코드의 의도를 파악하기 위해 전체 루프를 확인하고 이해해야 함
Stream
선언형 프로그래밍 스타일로, 무엇을(What) 처리할지 집중함
파이프라인으로 데이터가 흐르며 처리됨
각 단계, 줄마다 명확한 목적을 드러냄
3-3. 스트림에서는 부작용 없는 함수를 사용하라
병렬 처리: 부작용이 없는 코드는 안전하게 병렬로 실행할 수 있다코드의 의미가 명확해지고(가독성) 부작용이 없어 디버깅이 쉬워진다(유지보수)
❌ 잘못된 예시 - 스트림 안에서 외부 리스트 변경
외부 리스트 변경
병렬 스트림에서 예측 불가능한 결과 발생 가능능
🙆🏼♂️ 올바른 예 - Collect(수집기)를 이용하여 내부에서 결과 수집
collect()는 내부에서 안전하게 데이터를 모은다외부 상태에 영향을 주지 않음 -> 병렬 처리도 안전
🤨 4. 병렬 스트림에서 부작용이 왜 위험할까?
병렬 스트림(parallelStream())은 데이터를 여러 스레드에서 동시 처리한다
따라서 아래와 같은 문제가 발생!
동시성 문제
여러 스레드가 동시에 같은 자원을 변경하면서 충돌 발생하는 문제
ArrayList는 스레드 안전하지 않고, 여러 스레드가 동시에 호출하면 내부 구조가 꼬임 > 동시성 문제!
🙆🏼♂️ 순수 함수와 collect를 사용하라
실행 순서 불확실성
병렬 스트림은 성능 향상을 위해 스레드들이 나눠서 동시에 처리함
결과 출력 순서가 보장되지 않음
💨 향후 확장 포인트
Spring에서 Stream 사용 시 주의점
Stream을 사용하여 JPA 엔티티를 DTO로 깔끔하게 변환할 수 있다
Stream 처리 중 DB 트랜잭션이 끝나지 않도록 주의하자 (LazyLoding을 사용하는 경우 스트림 처리 중 세션이 닫힐 수도 있음)
Stream 처리 결과를 캐싱할 때는 불변성을 유지하는 것이 중요하다
🤖 최종 결론
스트림 내에서 부작용이 있는 함수를 사용하면 코드가 불분명해지고 병렬 처리 시 문제가 발생할 수 있으니 주의하자. for-each로 외부 상태를 수정하지 말고, collect, reduce와 같은 연산으로 결과를 모으자.
😶🌫️ 느낀점
어려웠지만 익숙해져야 하는 stream
Last updated