복잡한뇌구조마냥

[JAVA] Effective Java - 스트림은 주의해서 사용하라 (45) 본문

BE/JAVA

[JAVA] Effective Java - 스트림은 주의해서 사용하라 (45)

지금해냥 2025. 8. 24. 21:37

1. 스트림과 스트림 파이프라인

  • 스트림(Stream): 데이터 원소의 유한 혹은 무한 시퀀스.
  • 스트림 파이프라인(Stream Pipeline): 스트림 원소들로 수행하는 연산 단계의 묶음.
    • 소스(Source): 컬렉션, 배열, I/O 채널 등.
    • 중간 연산(Intermediate Operation): 변환, 필터링 등. 새로운 스트림을 반환.
    • 종단 연산(Terminal Operation): 합계, 수집, 탐색 등. 최종 결과 반환.
  • 지연 평가(Lazy Evaluation): 종단 연산이 호출될 때 연산이 실제 수행됨.
    • 종단 연산에 쓰이지 않는 원소는 아예 계산되지 않음.
    • 덕분에 무한 스트림도 다룰 수 있다.

✅ 스트림 API는 메서드 연쇄를 지원하는 플루언트 API라서 선언형 프로그래밍 스타일을 제공한다.


2. 스트림의 장점

  • 간결성: 반복문을 짧고 명확하게 대체 가능.
  • 의도 표현력: “무엇을 하고 싶은지”를 코드에 드러낸다.
  • 조합 가능성: filter → map → reduce 체이닝.
  • 병렬 처리 용이: .parallelStream()을 통한 병렬 연산 지원.
 
List<String> result = words.stream()
                           .filter(w -> w.length() > 3)
                           .map(String::toUpperCase)
                           .sorted()
                           .toList();

3. 스트림 사용 시 주의할 점

  • 가독성 저하 위험
    복잡한 파이프라인은 오히려 for문보다 읽기 힘들다.
  • 디버깅 어려움
    중간 연산 단계에서 값을 추적하기 힘들다.
  • 표현력 한계
    • break/continue 같은 제어 흐름은 스트림에서 난해하다.
    • 지역 변수 갱신 불가(람다는 final 또는 사실상 final만 접근 가능).
  • 혼란의 여지
    • char를 다루면 IntStream으로 반환되어 혼란스러울 수 있음 → 명시적 형변환 필요.
    • 람다 매개변수 이름을 의미 있게 정하지 않으면 가독성이 나빠짐.

4. 스트림이 빛나는 경우 ✨

  1. 원소들의 시퀀스를 일관되게 변환할 때
  2. 원소들을 조건에 따라 필터링할 때
  3. 원소들을 하나의 연산으로 집계할 때 (reduce)
  4. 원소들을 컬렉션에 모을 때 (collect)
  5. 원소 중 조건을 만족하는 값 탐색할 때 (findAny, findFirst)

5. 스트림이 힘든 경우 ⚠️

  • 한 데이터가 여러 단계를 거치며, 각 단계의 값에 동시에 접근해야 하는 경우
  • 복잡한 제어 흐름 (break, continue, 다중 분기) 이 필요한 경우
  • 로직을 추적해야 하는 절차적 문제 해결 코드

이런 상황은 오히려 전통적인 for문이 더 명확하다.

// 반복 방식
private static List<Card> new Deck() {
	List<Card> result = new ArrayList<>();
    for (Suit suit : Suit.values())
    	for (Rank rank : Rank.values())
        	result.add(new Card(suit, rank));
    return result;
}

// 스트림 방식
private static List<Card> new Deck() {
	return Stream.of(Suit.values())
    	.flatMap(suit ->
        	Stream.of(Rank.values())
            	.map(rank -> new Card(suit, rank)))
            .collect(toList());
}

6. 절충안: 스트림과 반복문은 보완재

  • 스트림과 반복문은 대체재가 아니라 보완재다.
  • 확고부동한 규칙은 없고, 상황에 따라 적절히 선택해야 한다.
  • 같은 로직을 스트림과 반복문 양쪽으로 작성해보고 더 읽기 좋은 쪽을 택하라.
  • 기존 코드를 리팩터링할 때는 새 코드가 더 짧고 명확해질 때만 스트림으로 바꾸는 것이 좋다.
  • 팀 컨벤션이나 동료 개발자의 선호도도 고려할 것.

7. 결론

스트림은 데이터를 다루는 강력한 도구이지만,
남용하면 오히려 코드가 읽기 어렵고 유지보수가 힘들어진다.
데이터 변환·필터링·집계에는 스트림을,
복잡한 제어 흐름이나 상태 갱신에는 반복문을 쓰는 게 낫다.
결국 중요한 것은 코드의 가독성과 명확성이다.

LIST