|

자바 stream과 for문 차이: 언제 더 읽기 쉽고 언제 오히려 복잡해질까

자바 stream과 for문 차이를 설명하는 대표 이미지
선언형 파이프라인이 빛나는 장면과 명시적 제어가 더 쉬운 장면은 다르다

자바 stream과 for문 차이는 자바를 조금만 써도 자주 다시 만나게 되는 주제입니다. 중요한 것은 무엇이 더 최신스러워 보이느냐가 아닙니다. 데이터 변환을 선언적으로 보여주고 싶은지, 아니면 중간 상태와 제어 흐름을 직접 드러내고 싶은지를 먼저 보면 선택이 훨씬 쉬워집니다.

이번 글에서는 filter, map, collect가 잘 맞는 장면과 break, continue, 상태 추적 때문에 for문이 더 읽기 쉬운 장면을 예시로 비교하겠습니다. 성능 신화보다 가독성과 유지보수 관점에 초점을 맞추겠습니다.


자바 stream과 for문 차이 한눈에 보기

자바 stream과 for문 차이 요약 카드
핵심 카드: 선언형 변환과 명시적 제어는 잘 맞는 장면이 다르다
  • stream은 filter → map → collect처럼 데이터 변환 파이프라인이 짧고 분명할 때 읽기 좋다
  • for문은 break, continue, 상태 추적, 디버깅이 중요할 때 더 직접적이다
  • stream이 더 현대적이라서 항상 좋은 것도 아니고, for문이 구식이라서 항상 나쁜 것도 아니다

stream은 무엇이 다를까

Oracle 문서 기준으로 stream은 컬렉션 자체가 아니라 source를 intermediate operation과 terminal operation으로 흘려 보내는 pipeline 추상화입니다. intermediate operation은 lazy하고, terminal operation이 실행될 때 실제 처리가 일어납니다.

List<String> emails = users.stream()
        .filter(User::isActive)
        .map(User::getEmail)
        .toList();

이 코드는 활성 사용자만 고르고, 이메일만 뽑고, 리스트로 만든다는 흐름이 바로 읽힙니다. 즉 stream은 무엇을 변환하는지를 앞에 두는 표현에 가깝습니다.


for문은 무엇이 다를까

List<String> emails = new ArrayList<>();
for (User user : users) {
    if (!user.isActive()) {
        continue;
    }
    emails.add(user.getEmail());
}

for문은 조금 더 길어질 수 있지만, 어느 줄에서 건너뛰는지와 어떤 순서로 처리하는지가 분명합니다. 로그를 넣거나 브레이크포인트를 걸기도 쉽습니다. 즉 for문은 절차를 보여주는 코드에 더 가깝습니다.


for문이 더 읽기 좋은 장면

User found = null;
for (User user : users) {
    if (user.isBanned()) {
        continue;
    }
    if (user.getScore() > 90) {
        found = user;
        break;
    }
}

break, continue, return이 중요할 때는 for문이 훨씬 직접적입니다. stream으로도 findFirst() 같은 표현이 가능하지만, 분기가 늘어나면 읽기 속도가 금방 떨어질 수 있습니다.

int streak = 0;
for (int score : scores) {
    if (score >= 80) {
        streak++;
    } else {
        streak = 0;
    }

    if (streak == 3) {
        System.out.println("연속 3회 통과");
        break;
    }
}

이런 코드는 데이터 변환보다 상태 추적이 핵심입니다. stream으로 옮기면 로직 자체보다 표현 요령이 더 눈에 띄기 쉽습니다.


side effect와 성능은 왜 조심해야 할까

Oracle Stream API는 behavioral parameter가 non-interfering, stateless 해야 한다고 설명합니다. 그래서 side effect 중심으로 stream을 쓰기 시작하면 선언형 흐름의 장점이 줄어듭니다.

또한 stream이 항상 빠르다, for문이 항상 빠르다 같은 문장은 둘 다 위험합니다. 데이터 크기, 박싱, JIT, 병렬화 비용에 따라 달라질 수 있으므로 읽기 쉬운 기본 구현을 먼저 만들고 병목이면 측정으로 확인하는 편이 안전합니다.

자바 stream 선택 체크리스트 카드
짧은 체크리스트: 변환 중심이면 stream, 제어 중심이면 for문

정리

자바 stream과 for문 차이는 우열보다 역할 차이로 보는 편이 정확합니다. stream은 데이터 변환 파이프라인을 짧고 선언적으로 보여줄 때 강하고, for문은 분기, 조기 종료, 상태 추적, 디버깅처럼 절차를 직접 드러내야 할 때 강합니다.

결국 중요한 기준은 하나입니다. 더 현대적으로 보이는 문법이 아니라, 지금 이 문제의 의도를 더 빨리 보여주는 문법을 고르는 것입니다.

관련해서 자바의 타입과 선택 기준을 더 보고 싶다면 자바 Optional은 왜 자주 오해될까, 자바 String, StringBuilder, StringBuffer 차이, 자바 ArrayList와 LinkedList 차이, 자바 ==와 equals 차이도 같이 읽어보면 좋습니다.

공식 기준은 Oracle Java SE 21 stream package summaryStream API를 참고했습니다.

함께보면 좋은 글