Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to jump out of the method inside Java 8 Iterator.forEachRemaining loop?

The simplified code is below. I expected that return inside the iteration will result in jumping out of the method. Instead the code executes the next iteration and then the print after iterations within the method
 

public static void callIter() {
    List<String> names = new ArrayList<>();
    names.add("A");
    names.add("B");
    names.add("C");
    ListIterator<String> nameIterator = names.listIterator();
    nameIterator.forEachRemaining(name -> {
        if (name.equals("B")) {
            System.out.println("Found");
            return;
        }
        System.out.println(name);
    });
    System.out.println("AfterIter");
}
like image 936
Alex Avatar asked Jan 05 '18 15:01

Alex


3 Answers

The return finishes execution of the code that is executed for each (remaining) element of the iterator. It does not stop execution of the iteration/forEachRemaining. This matches the behaviour you are describing.

The Javadoc for forEachRemaining states:

Performs the given action for each remaining element until all elements have been processed or the action throws an exception. Actions are performed in the order of iteration, if that order is specified. Exceptions thrown by the action are relayed to the caller.

In other words, if you don't want to abuse exceptions for this, you should not use forEachRemaining if you intend to stop the iteration before processing the last element.

like image 140
C-Otto Avatar answered Nov 17 '22 10:11

C-Otto


The forEach methods, whether Iterable.forEach, Iterator.forEachRemaining or Stream.forEach are intended to do what the name suggests, apply an action for each element, not just some.

Stopping the iteration is not forseen by this API, which can be recognized by looking at the functional signature of the Consumer interface, which is (T) -> void, not containg any possibility for the collection or iterator to notice a stop condition.

At this place, you should reconsider whether you are using the right tool for the job. Compare your approach with, e.g.

List<String> names = Arrays.asList("A", "B", "C");
int ix = names.indexOf("B");
(ix<0? names: names.subList(0, ix)).forEach(System.out::println);
if(ix>=0) System.out.println("Found");
System.out.println("AfterIter");

Of course, if the printing of the elements was only for debugging purposes, the actual operation simplifies to

List<String> names = Arrays.asList("A", "B", "C");
if(names.contains("B")) System.out.println("Found");
System.out.println("AfterIter");

If equality was only a placeholder for an arbitrary predicate, you may use

List<String> names = Arrays.asList("A", "B", "C");
if(names.stream().anyMatch(s -> s.equals("B"))) System.out.println("Found");
System.out.println("AfterIter");

which can be adapted to arbitrary conditions.

This can be expanded to

List<String> names = Arrays.asList("A", "B", "C");
Optional<String> match = names.stream()
    .peek(System.out::println)
    .filter(Predicate.isEqual("B"))
    .findFirst();

if(match.isPresent()) System.out.println("Found");
// or
match.ifPresent(s -> System.out.println("Found "+s));

System.out.println("AfterIter");

showing, how you can debug the processing by printing the elements, an alternative way to express the equality predicate (still can be replaced by other predicates), and how to get the actual matching element for nontrivial predicates.

Generally, if you want to utilize the new APIs of Java 8, don’t try to write your logic in terms of forEach. Rather, try to avoid forEach, using more appropriate operations whenever possible.

like image 23
Holger Avatar answered Nov 17 '22 10:11

Holger


forEachRemaining does not have option to break iteration. You can do search through stream operations, like this:

String name = names.stream().filter(name -> name.equals("B")).findFirst().orElse(null);
like image 1
Anton Tupy Avatar answered Nov 17 '22 11:11

Anton Tupy