Java Stream – dropWhile로 break 대체하기

Java
avatar
2025.12.08
·
6 min read

들어가며

Java 9에서 스트림 API에 새로 추가된 dropWhile()는 평소에 잘 안 쓰이지만, 특정 상황에서는 기존의 반복문(for, while)보다 훨씬 깔끔한 코드를 만들 수 있다.

반복을 하다가 처음으로 조건에 맞지 않는 순간 이후 데이터는 모두 버리고 싶을 때, 혹은 조건에 맞는 순간부터 로직을 처리하고 싶을 때, 기존에는 직접 break를 사용하거나 상태 변수(flag)를 둬서 처리해야 했다.

기존의 for-continue 반복문

예를 들어 주식의 장중 틱 데이터가 시간 오름차순으로 들어오고 있고, 장 종료 시각(예: 15:30) 이후 데이터만 조회하고 싶다고 하자.

public List<Tick> getAfterMarketTicks(List<Tick> ticks) {
    List<Tick> validTicks = new ArrayList<>();

    for (Tick tick : ticks) {
        if (isAfterMarket(tick)) {
            continue;
        }

        validTicks.add(tick);
    }

    return validTicks;
}

private boolean isAfterMarket(Tick tick) {
    return tick.getDateTime().isAfter(MARKET_CLOSE_TIME);
}

기존에는 이렇게 매번 체크 후 continue 를 통해 원치 않는 데이터를 스킵해주어야 했다.

틱 데이터는 시간으로 오름차순으로 정렬되어있기 때문에, 원하는 시간대 이후 데이터를 찾았음에도 매번 시간을 비교하는 것은 비효율적이므로, 다음과 같이 리팩토링해볼 수 있다.

public List<Tick> getAfterMarketTicks(List<Tick> ticks) {
    List<Tick> validTicks = new ArrayList<>();
    boolean foundAfterMarket = false;

    for (Tick tick : ticks) {
        if (!foundAfterMarket || isAfterMarket(tick)) {
            continue;
        }
        
        flag = true;
        validTicks.add(tick);
    }

    return validTicks;
}

private boolean isAfterMarket(Tick tick) {
    return tick.getDateTime().isAfter(MARKET_CLOSE_TIME);
}

이러면 이제 매번 시간을 비교하지는 않을 수 있게 됐지만, 코드가 꽤 길어지게 되었고 불변스럽지 못한 코드가 되었다.

dropWhile을 사용한 반복문

dropWhile

dropWhile은 Java 9에 추가된 스트림 연산 함수이다.

Stream<T> dropWhile(Predicate<? super T> predicate)
  • 조건이 참인 동안 요소를 드롭(drop)한다.

  • 조건이 처음으로 거짓이 되는 시점부터 남은 모든 요소를 통과시킨다.

  • 이미 조건이 거짓이 되고 난 이후에는 필터링이 일어나지 않는다.

이 스트림이 정렬된 경우, 주어진 술어를 만족하는 요소들의 가장 긴 접두사를 제거한 후 남은 요소들로 구성된 스트림을 반환합니다. 그렇지 않은 경우, 이 스트림이 정렬되지 않은 경우, 주어진 술어를 만족하는 요소들의 부분 집합을 제거한 후 남은 요소들로 구성된 스트림을 반환합니다.

Stream (Java SE 21 & JDK 21)
declaration: module: java.base, package: java.util.stream, interface: Stream
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html#dropWhile(java.util.function.Predicate)

바꾼 결과

public List<Tick> getAfterMarketTicks(List<Tick> ticks) {
    return ticks.stream()
        .dropWhile(tick -> !isAfterMarket(tick))
        .toList();
}

private boolean isAfterMarket(Tick tick) {
    return tick.getDateTime().isAfter(MARKET_CLOSE_TIME);
}

틱이 isAfterMarket을 만족하지 않을 동안의 틱은 전부 버리고, 그 후의 틱만 얻어오는 코드를 다음과 같이 선언적으로 작성할 수 있다.

takeWhile을 사용한 반복문

takeWhile 또한 dropWhile과 비슷하지만, 조건이 거짓이 된 이후의 요소를 전부 드롭하는 정반대의 행동을 한다.

다시 말해 아까의 예제를 이렇게 사용할 수도 있다.

public List<Tick> getAfterMarketTicks(List<Tick> ticks) {
    return ticks.stream()
        .takeWhile(tick -> isAfterMarket(tick))
        .toList();
}

private boolean isAfterMarket(Tick tick) {
    return tick.getDateTime().isAfter(MARKET_CLOSE_TIME);
}

이렇게 코드를 짜면, 장 이후의 틱을 전부 수용하겠다는 뜻이 된다.

또한 이렇게 스트림의 takeWhile, dropWhile을 이용해 짤 경우, 반환되는 값이 스트림이기 때문에 추가적인 반복문 없이도 로직을 추가하기 용이하다.

// 장 종료 이후의 거래량 구하기
public long getAfterMarketTradeVolume(List<Tick> ticks) {
    return ticks.stream()
        .takeWhile(tick -> isAfterMarket(tick))
        .mapToLong(Tick::getVolume) // 장 종료 이후의 틱을 필터링 한 후, 거래량을 합한다
        .sum();
}

private boolean isAfterMarket(Tick tick) {
    return tick.getDateTime().isAfter(MARKET_CLOSE_TIME);
}

추가 예제

사용해볼법한 예제를 모아 보았다.

장시간 배치 로그에서 본격 작업 시작 이후만 보기

----- START ----- 이후만 파싱

List<String> mainProcessLogs = logs.stream()
    .dropWhile(line -> !line.equals("----- START -----"))
    .skip(1) // 마커 건너뛰기
    .toList();

HTML 파일에서 <body> 이후 부분만 추출

List<String> bodyLines = html.stream()
    .dropWhile(line -> !line.contains("<body>"))
    .skip(1)
    .toList();

healthcheck 성공만 나오다가 최초 실패 이후 구간만 보기

List<String> failing = logs.stream()
    .dropWhile(line -> line.contains("200 OK"))
    .toList();






- 컬렉션 아티클