Java 8 Stream API 실무 적용 후기

배경

회사 프로젝트가 Java 7에서 8로 마이그레이션되면서 Stream API를 본격적으로 사용하기 시작했다. 기존 for문과 if문으로 가득한 코드를 Stream으로 리팩토링하는 작업을 진행했는데, 생각보다 고려할 점이 많았다.

발생한 문제

대량의 데이터를 처리하는 배치 작업에서 기존 for문을 Stream으로 변경했더니 오히려 처리 시간이 증가하는 현상이 발생했다.

// 기존 코드
List<Order> result = new ArrayList<>();
for (Order order : orders) {
    if (order.getAmount() > 10000) {
        result.add(order);
    }
}

// 변경 후 (문제 발생)
List<Order> result = orders.stream()
    .filter(order -> order.getAmount() > 10000)
    .collect(Collectors.toList());

원인 분석

프로파일링 결과, Stream의 오버헤드가 단순 반복문보다 크다는 것을 확인했다. 특히 작은 컬렉션을 처리할 때는 오히려 성능이 떨어졌다. Stream은 내부적으로 Spliterator, 함수형 인터페이스 객체 생성 등의 추가 비용이 발생한다.

적용 기준

이후 다음과 같은 기준을 세웠다:

  • 단순 반복문은 그대로 유지
  • map, filter, reduce 등 여러 연산을 체이닝할 때만 Stream 사용
  • 병렬 처리가 필요한 대용량 데이터는 parallelStream 고려
  • 코드 가독성과 성능 사이에서 trade-off 판단
// Stream을 쓰면 좋은 경우
Map<String, List<Order>> grouped = orders.stream()
    .filter(o -> o.getStatus() == Status.COMPLETED)
    .collect(Collectors.groupingBy(Order::getCustomerId));

결론

Stream API는 강력하지만 만능은 아니다. 특히 레거시 코드를 리팩토링할 때는 성능 테스트를 반드시 병행해야 한다. 새로운 문법이라고 무조건 좋은 것은 아니라는 교훈을 얻었다.