본문 바로가기
언어 공부/JAVA

JAVA D2 [ 반복문 ] - 실무에서 사용하는 반복문 정리

by TMrare 2025. 3. 5.

실무에서 사용하는 자바 반복문 총정리

💡 반복문 총정리 목적: 실무에서 자주 활용되는 반복문 패턴, 성능 최적화, 그리고 현대적 대안까지 한눈에 파악할 수 있도록 정리함.

반복문 실무 활용 비교

반복문 실무 적용 시나리오 장점 단점
while • API 응답 대기
• 파일 읽기 작업
• 사용자 입력 검증
• 재시도 로직
• 종료 조건이 복잡한 경우 적합
• 외부 요인에 의존적인 반복에 효과적
• 무한 루프 위험성
• 구조가 명확하지 않을 수 있음
for • 배치 처리
• 페이지네이션
• 인덱스 활용 필요
• 다차원 배열 처리
• 범위가 명확할 때 가독성 우수
• 루프 변수 범위 제한으로 안전
• 복잡한 종료 조건에는 적합하지 않음
• 외부 상태 변경 시 주의 필요
for-each • DTO 변환
• 데이터 매핑
• 모든 요소 동일 처리
• 함수형 스타일 코드
• 가장 간결한 문법
• 인덱스 오류 방지
• 의도 명확히 표현
• 인덱스 접근 불가
• 원본 컬렉션 수정 불가
• 역순 순회 불가

실무에서 유용한 반복문 패턴

🔄 API 폴링 패턴

네트워크 요청, 작업 완료 대기 등에서 타임아웃과 재시도 로직을 포함한 패턴

long startTime = System.currentTimeMillis();
long timeout = 30000; // 30초 타임아웃
boolean completed = false;
int retries = 0;

while (!completed && System.currentTimeMillis() - startTime < timeout) {
    try {
        Response response = api.checkStatus(jobId);
        if (response.getStatus() == Status.COMPLETED) {
            completed = true;
            processResult(response.getData());
        } else {
            // 진행 중인 경우 대기
            retries++;
            Thread.sleep(Math.min(1000 * retries, 10000)); // 점진적 대기
        }
    } catch (Exception e) {
        retries++;
        if (retries >= MAX_RETRIES) {
            throw new Exception("최대 재시도 횟수 초과");
        }
    }
}

성능 팁: 지수 백오프(exponential backoff) 전략으로 재시도 간격을 조정해 시스템 부하를 줄임.

🔢 청크 처리 패턴

대용량 데이터를 메모리 효율적으로 처리하기 위한 청크 단위 배치 처리 패턴

int totalItems = repository.countItems();
int pageSize = 1000;
int totalPages = (int) Math.ceil((double) totalItems / pageSize);

// 트랜잭션 및 메모리 관리를 위한 청크 단위 처리
for (int page = 0; page < totalPages; page++) {
    int offset = page * pageSize;
    
    // 현재 청크의 데이터만 로드
    List chunk = repository.findItems(offset, pageSize);
    
    // 처리 로직
    for (Item item : chunk) {
        processItem(item);
    }
    
    // 메모리 정리
    chunk.clear();
}

주의사항: 페이지 처리 시 정렬 조건이 없으면 데이터 일관성 문제가 발생할 수 있음.

📋 DTO 변환 패턴

Entity에서 DTO로 변환하거나 데이터 매핑을 처리하는 패턴

// Entity에서 DTO로 변환하는 예제
List userDTOs = new ArrayList<>();
for (User user : users) {
    UserDTO dto = new UserDTO();
    dto.setId(user.getId());
    dto.setName(user.getName());
    dto.setEmail(user.getEmail());
    userDTOs.add(dto);
}

// 스트림 API 사용 버전
List streamDTOs = users.stream()
    .map(user -> {
        UserDTO dto = new UserDTO();
        dto.setId(user.getId());
        dto.setName(user.getName());
        dto.setEmail(user.getEmail());
        return dto;
    })
    .collect(Collectors.toList());

최적화 포인트: 대량의 데이터를 처리할 때는 스트림 API의 병렬 처리 기능을 활용할 수 있음.

🔍 이진 탐색 패턴

정렬된 데이터에서 효율적으로 원하는 값을 찾는 패턴

// 정렬된 리스트에서 이진 탐색
int binarySearch(List sortedList, int target) {
    int left = 0;
    int right = sortedList.size() - 1;
    
    while (left <= right) {
        // 오버플로우 방지 중간값 계산
        int mid = left + (right - left) / 2;
        int value = sortedList.get(mid);
        
        if (value == target) {
            return mid;  // 찾음
        } else if (value < target) {
            left = mid + 1;  // 오른쪽 반에서 계속 탐색
        } else {
            right = mid - 1;  // 왼쪽 반에서 계속 탐색
        }
    }
    
    return -1;  // 못 찾음
}

반복문 성능 최적화

성능 병목 해결 패턴

루프 호이스팅 (Loop Hoisting)

// 비효율적인 코드
for (int i = 0; i < list.size(); i++) {
    // list.size()가 매 반복마다 호출됨
    process(list.get(i));
}

// 최적화된 코드
int size = list.size(); // 반복문 밖으로 이동
for (int i = 0; i < size; i++) {
    process(list.get(i));
}

효과: 불필요한 메서드 호출 제거, JIT 최적화 향상

루프 퓨전 (Loop Fusion)

// 비효율적인 코드: 두 개의 반복문
for (int i = 0; i < data.length; i++) {
    data[i] = transform1(data[i]);
}
for (int i = 0; i < data.length; i++) {
    data[i] = transform2(data[i]);
}

// 최적화된 코드: 하나의 반복문으로 병합
for (int i = 0; i < data.length; i++) {
    data[i] = transform1(data[i]);
    data[i] = transform2(data[i]);
}

효과: 메모리 접근 최소화, 캐시 효율성 증가

조기 종료 최적화

// 모든 조건을 무조건 검사
boolean isValid = true;
for (int i = 0; i < conditions.length; i++) {
    if (!checkCondition(conditions[i])) {
        isValid = false;
    }
}
return isValid;

// 조기 종료로 최적화
for (int i = 0; i < conditions.length; i++) {
    if (!checkCondition(conditions[i])) {
        return false; // 첫 번째 실패 시 즉시 종료
    }
}
return true;

효과: 불필요한 연산 제거, CPU 및 리소스 절약

향상된 for문 사용

// 기존 인덱스 기반 for문
for (int i = 0; i < items.size(); i++) {
    Item item = items.get(i);
    process(item);
}

// 향상된 for문 (for-each)
for (Item item : items) {
    process(item);
}

효과: 코드 간결화, 인덱스 오류 방지, 내부적으로 Iterator 사용으로 효율적

현대적 대안: 함수형 스트림 API

Java 8부터 도입된 Stream API는 선언적이고 함수형 방식으로 컬렉션을 처리함. 가독성이 높고 병렬 처리가 용이한 장점이 있음.

// 기존 명령형 방식
List highValueOrders = new ArrayList<>();
for (Order order : orders) {
    if (order.getStatus() == Status.COMPLETED && order.getValue() > 10000) {
        Customer customer = customerService.getCustomer(order.getCustomerId());
        if (customer.getTier() == CustomerTier.PREMIUM) {
            highValueOrders.add(order);
        }
    }
}
Collections.sort(highValueOrders, 
    Comparator.comparing(Order::getCreatedAt).reversed());

// 최신 함수형 스트림 방식
List highValueOrdersStream = orders.stream()
    .filter(order -> order.getStatus() == Status.COMPLETED)
    .filter(order -> order.getValue() > 10000)
    .filter(order -> customerService.getCustomer(order.getCustomerId())
                              .getTier() == CustomerTier.PREMIUM)
    .sorted(Comparator.comparing(Order::getCreatedAt).reversed())
    .collect(Collectors.toList());

// 병렬 처리 - 대용량 데이터셋에 효과적
List parallelProcessed = orders.parallelStream()
    // ... 동일한 연산 체인 ...
    .collect(Collectors.toList());

스트림 API 주의사항

  • 소규모 데이터셋에서는 일반 반복문이 오히려 빠를 수 있음
  • 병렬 스트림은 CPU 코어 수와 데이터 분할 비용에 따라 효율이 달라짐
  • 상태 변경이 많은 연산은 병렬 스트림에 부적합함
  • stateful 람다 표현식은 병렬 처리 시 예상치 못한 결과를 초래할 수 있음

스트림 API 최적 사용 시나리오

  • 데이터 변환 및 매핑 작업
  • 필터링 및 검색 작업
  • 집계 및 통계 계산
  • 다단계 데이터 처리 파이프라인
  • 대규모 독립적 요소 처리

실무 응용: 반복문 리팩토링 체크리스트

체크포인트 방법 이점
반복문이 한 가지 책임만 갖는가? 단일 책임 원칙(SRP)에 따라 하나의 루프는 하나의 작업만 수행하도록 분리함 가독성 향상, 유지보수 용이성, 버그 감소
루프 내 객체 생성을 최소화했는가? 객체 생성을 루프 밖으로 이동하거나 객체 풀링 기법을 활용함 GC 부하 감소, 메모리 효율성 향상
반복문 최적화 기법을 적용했는가? 루프 호이스팅, 루프 퓨전, 조기 종료 등의 최적화 기법 적용 성능 향상, 리소스 사용 효율화
함수형 스트림 API 전환이 적합한가? 데이터 처리 로직이 복잡한 경우 함수형 스트림 API 활용 검토 코드 가독성 향상, 병렬 처리 용이성

결론

자바 반복문은 실무에서 가장 많이 사용되는 제어 구조 중 하나로, 상황에 맞게 적절한 유형을 선택하는 것이 중요함. while은 종료 조건이 불명확할 때, for는 반복 횟수가 명확할 때, for-each는 모든 요소를 순회할 때 적합함. 성능 최적화 기법을 적용하고 현대적인 함수형 스트림 API를 적절히 활용하면 코드의 가독성과 효율성을 모두 향상시킬 수 있음.

💡 실무 팁: 코드 가독성과 유지보수성을 우선시하되, 성능 중요 구간에서는 최적화 기법을 적용하는 균형 잡힌 접근이 필요함.