39

I have a java 8 stream loop with the following content:

    void matchSellOrder(Market market, Order sellOrder) {
        System.out.println("selling " + market.pair() + " : " + sellOrder);

        market.buyOrders()
                .stream()
                .filter(buyOrder -> buyOrder.price >= sellOrder.price)
                .sorted(BY_ASCENDING_PRICE)
                .forEach((buyOrder) -> {
                    double tradeVolume = Math.min(buyOrder.quantity, sellOrder.quantity);
                    double price = buyOrder.price;

                    buyOrder.quantity -= tradeVolume;
                    sellOrder.quantity -= tradeVolume;

                    Trade trade = new Trade.Builder(market, price, tradeVolume, Trade.Type.SELL).build();
                    CommonUtil.convertToJSON(trade);

                    if (sellOrder.quantity == 0) {
                        System.out.println("order fulfilled");
                        // break loop there
                    }
                });
    }

How can I break out of loop when some condition is met? Whats the right way to close stream anyway?

UPDATE

I was misusing streams tecnique assuming that it is a loop, it is not designed for that. Here's the code I've ended up using answer provided below:

        List<Order> applicableSortedBuyOrders = market.buyOrders()
                .stream()
                .filter(buyOrder -> buyOrder.price >= sellOrder.price)
                .sorted(BY_ASCENDING_PRICE)
                .collect(toList());

        for(Order buyOrder : applicableSortedBuyOrders){
            double tradeVolume = Math.min(buyOrder.quantity, sellOrder.quantity);
            double price = buyOrder.price;

            buyOrder.quantity -= tradeVolume;
            sellOrder.quantity -= tradeVolume;

            Trade trade = new Trade.Builder(market, price, tradeVolume, Trade.Type.SELL).build();
            CommonUtil.printAsJSON(trade);

            if (sellOrder.quantity == 0) {
                System.out.println("order fulfilled");
                break;
            }
        }
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
vach
  • 10,571
  • 12
  • 68
  • 106
  • 1
    This question IS a duplicate, but not so much of the question indicated, but of this one: http://stackoverflow.com/questions/23308193/how-to-break-or-return-from-java8-lambda-foreach – Fritz Duchardt Jul 09 '15 at 09:36
  • I've not tried this but...What if you perform your work in a `peek()` consumer and follow it with an `anyMatch()` predicate on a concurrency-safe termination state object? – simon.watts Sep 01 '17 at 08:12
  • 1
    you may place a `sequential()` before your terminal operation to avoid concurrency problems. then just replace `forEach` with `anyMatch` and return inside the predicate `return sellOrder.quantity == 0` – benez Sep 21 '17 at 11:31

2 Answers2

52

Stream.forEach is not a loop and it's not designed for being terminated using something like break. If the stream is a parallel stream the lambda body could be executed on different threads at the same time (not easy to break that and it could easily produce incorrect results).

Better use a iterator with a while loop:

Iterator<BuyOrderType> iter = market.buyOrders() // replace BuyOrderType with correct type here
            .stream()
            .filter(buyOrder -> buyOrder.price >= sellOrder.price)
            .sorted(BY_ASCENDING_PRICE).iterator();
while (iter.hasNext()) {
    BuyOrderType buyOrder = iter.next()  // replace BuyOrderType with correct type here
    double tradeVolume = Math.min(buyOrder.quantity, sellOrder.quantity);
    double price = buyOrder.price;

    buyOrder.quantity -= tradeVolume;
    sellOrder.quantity -= tradeVolume;

    Trade trade = new Trade.Builder(market, price, tradeVolume, Trade.Type.SELL).build();
    CommonUtil.convertToJSON(trade);

    if (sellOrder.quantity == 0) {
        System.out.println("order fulfilled");
        break;
    }
}
Ynjxsjmh
  • 28,441
  • 6
  • 34
  • 52
fabian
  • 80,457
  • 12
  • 86
  • 114
  • 1
    thanks, seems i was misusing new feature. – vach Jun 02 '14 at 14:52
  • P.S. both are of the same type "Order" – vach Jun 02 '14 at 14:55
  • I agree with this answer, except for the first statement. Stream.forEach is a loop. The difference is that it's an internal loop, instead of an external loop. Because the loop is internal you cannot call break on it. – Bogoth Oct 05 '15 at 16:47
  • 2
    o ye of little faith! you totally can break out of lambda loop: `stream.allMatch(value -> { if (...) return true; /* continue */ else return false; /* break */ });` – yk4ever Jun 30 '16 at 10:57
  • 3
    @yk4ever : Tell this to a parallel steam! E.g. `IntStream.range(0, 1000).boxed().parallel().allMatch(i -> { System.out.println(i); return i % 2 != 0; });` – fabian Jun 30 '16 at 11:15
  • I think everyone using Streams needs to read this answer! – The Coordinator Nov 20 '16 at 03:51
  • @fabian: this also works with a parallel stream: each threaded task will stop as soon as it finds the first element that returns `false`. – rudi May 14 '19 at 13:06
  • @yk4ever: the only drawback, you can't use any other terminal operation on the stream anymore. – rudi May 14 '19 at 13:07
  • @fabian: then don't use a parallel stream – Matt Cosentino May 24 '20 at 15:12
27

Well, there is no method to do this in the stream api, (as far as i know).

But if you really need it, you can use an Exception.

EDIT: For the people giving -1 to this answer I'm not advertising this as an approach one should follow, it's just an option for the cases where you need it, and it does answer the question.

public class BreakException extends RuntimeException {...}

try {
    market.buyOrders()
            .stream()
            .filter(buyOrder -> buyOrder.price >= sellOrder.price)
            .sorted(BY_ASCENDING_PRICE)
            .forEach((buyOrder) -> {
                double tradeVolume = Math.min(buyOrder.quantity, sellOrder.quantity);
                double price = buyOrder.price;

                buyOrder.quantity -= tradeVolume;
                sellOrder.quantity -= tradeVolume;

                Trade trade = new Trade.Builder(market, price, tradeVolume, Trade.Type.SELL).build();
                CommonUtil.convertToJSON(trade);

                if (sellOrder.quantity == 0) {
                    System.out.println("order fulfilled");
                    throw new BreakException()
                }
            });
} catch (BreakException e) {
    //Stoped
}
Tiago Engel
  • 3,533
  • 1
  • 17
  • 22
  • 6
    This is an option too, but as far as I know throwing and handilng exception are too expensive operations in terms of performance and should be avoided (as well as finally blocks mentioned above) – vach Jun 02 '14 at 14:53
  • 1
    Won't work with parallel streams. – rudi May 14 '19 at 13:09
  • Using exceptions to control program flow is not a good practice. – clapas Nov 07 '22 at 12:07