JAVA

[Java] Stream vs 일반 for문 (List<Map<String, Object>> 구조)

도미노& 2025. 3. 25. 15:15

최근 한 프로젝트에서 로직이 복잡한 배치 프로그램에서 에러가 났다.

디버깅하려고 보니 Stream을 이용해 코딩한 것.

일반 for문으로 코드를 수정한 뒤에 디버깅을 해 오류를 해결했다.

 

Stream과 일반 for문의 차이를 메모하고자 작성하는 포스팅.

 

  for문 사용 Stream 사용
장점
  • 직관적이고 디버깅 쉬움
  • break, continue 사용 가능
  • 선언적이고 깔끔한 코드
  • 가독성 좋고 체이닝 쉬움
단점
  • 가독성 떨어질 수 있음 (특히 중첩되면) 
  • 디버깅이 어렵고, 예외 처리 불편할 수 있음
  • 너무 복잡하게 쓰면 가독성 저하
디버깅 쉬움 어려움
추천 사용법 복잡한 조건 or 상태 변경 단순 필터/맵핑

 

 

 

 

예제로 비교해 보자.


공통 샘플 데이터

List<Map<String, Object>> people = new ArrayList<>();

people.add(Map.of("name", "철수", "age", 28, "team", "A", "score", 85));
people.add(Map.of("name", "영희", "age", 32, "team", "B", "score", 90));
people.add(Map.of("name", "민수", "age", 40, "team", "A", "score", 70));
people.add(Map.of("name", "지영", "age", 29, "team", "B", "score", 95));
people.add(Map.of("name", "수지", "age", 35, "team", "C", "score", 88));

1. if문 조건에 따른 로직(println)

//--# for문
for (Map<String, Object> person : people) {
    Integer age = (Integer) person.get("age");
    if (age >= 30) {
        System.out.println(person.get("name"));
    }
}

//--# Stream
people.stream()
    .filter(p -> (Integer) p.get("age") >= 30)
    .map(p -> (String) p.get("name"))
    .forEach(System.out::println);

2. 값 변경 없는 정렬 - "score" 기준 내림차순

//--# for문
people.sort((p1, p2) -> ((Integer) p2.get("score")) - ((Integer) p1.get("score")));

//--# Stream
List<Map<String, Object>> sorted =
    people.stream()
          .sorted((p1, p2) -> ((Integer) p2.get("score")) - ((Integer) p1.get("score")))
          .collect(Collectors.toList());

3. 값 변경 있는 정렬 - "grade" 추가 후 "score" 기준 내림차순

//--# for문 : 원본 리스트 변경함
for (Map<String, Object> person : people) {
    int score = (Integer) person.get("score");
    String grade;
    if (score >= 90) {
        grade = "A";
    } else if (score >= 80) {
        grade = "B";
    } else {
        grade = "C";
    }
    person.put("grade", grade);
}

// 정렬 (score 기준 내림차순)
people.sort((p1, p2) -> ((Integer) p2.get("score")) - ((Integer) p1.get("score")));


//--# Stream : 	새 리스트 생성 (collect)
List<Map<String, Object>> updatedAndSorted = people.stream()
    .peek(person -> {
        int score = (Integer) person.get("score");
        String grade;
        if (score >= 90) grade = "A";
        else if (score >= 80) grade = "B";
        else grade = "C";
        person.put("grade", grade); // 값 변경
    })
    .sorted((p1, p2) -> ((Integer) p2.get("score")) - ((Integer) p1.get("score")))
    .collect(Collectors.toList());

 


 

✅ stream의 peek()의 원래 목적 : 값을 바꾸거나 외부에 영향을 주는 용도가 아님.

peek()은 중간 연산자로, Stream 안에서 "디버깅용 또는 가볍게 훑어보는 용도"로 만들어졌다.

stream.peek(System.out::println)

 

그렇지만 실무에서는 편해서 자주 쓴다고 한다.

stream
  .peek(e -> e.put("grade", "A"))  // Map에 새로운 값 추가
  .collect(Collectors.toList());

 


4. 그룹핑 - team별 그룹

//--# for문
Map<String, List<Map<String, Object>>> grouped = new HashMap<>();

for (Map<String, Object> person : people) {
    String team = (String) person.get("team");
    grouped.computeIfAbsent(team, k -> new ArrayList<>()).add(person);
}

//--# Stream
Map<String, List<Map<String, Object>>> grouped =
    people.stream()
          .collect(Collectors.groupingBy(p -> (String) p.get("team")));

5. 집계 (전체 score 합계 & 평균)

//--# for문
int sum = 0;
for (Map<String, Object> person : people) {
    sum += (Integer) person.get("score");
}
double avg = (double) sum / people.size();


//--# Stream
int sum = people.stream()
                .mapToInt(p -> (Integer) p.get("score"))
                .sum();

double avg = people.stream()
                   .mapToInt(p -> (Integer) p.get("score"))
                   .average()
                   .orElse(0.0);