Oleksandr Gavenko's blog
2017-03-26 23:00 Mimic finally block in Java loop honoring continue statement

Maintaining code with checks first and keeping all on a single level can be tricky because language designers don't worried about such use case.

Let's look to task for reporting that some adjusted elements in sequence have certain properties.

That would be a report on flights with moved departure time. We have full history of all flights in itinerary:

  public static class Flight {
      public String from;
      public String to;
      public String datetime;
      public Status status;
      public Flight(String from, String to, String details, Status status) {
          this.from = from;
          this.to = to;
          this.datetime = details;
          this.status = status;
      }
  }

  public enum Status {
      AVAILABLE, CANCELED;
  }

  public static void main(String[] args) {
      List<Flight> travel = Arrays.asList(
              new Flight("TLV", "ABC", "12:30", Status.AVAILABLE),
              new Flight("ABC", "XYZ", "11:20", Status.CANCELED),
              new Flight("ABC", "XYZ", "11:15", Status.CANCELED),
              new Flight("ABC", "XYZ", "12:20", Status.AVAILABLE),
              new Flight("XYZ", "ABC", "06:30", Status.CANCELED),
              new Flight("XYZ", "ABC", "20:15", Status.AVAILABLE),
              new Flight("ABC", "TLV", "12:30", Status.AVAILABLE));
}

Report will look like:

[flight: ABC=>XYZ: time: 11:15->12:20], [flight: XYZ=>ABC: time: 06:30->20:15]

with helpers:

private static String formatNote(String fromAirport, String toAirport, String oldDateTime, String newDateTime) {
    return String.format("[flight: %s=>%s: time: %s->%s]",
            fromAirport, toAirport, oldDateTime, newDateTime);
}

private static void dumpNotes(List<String> notes) {
    String msg = String.join(", ", notes);
    System.out.println(msg);
}

Naive attempt to follow introduced principle leads to code duplication:

private static List<String> duplicatedAssignments(List<Flight> travel) {
    List<String> notes = new LinkedList<>();
    Flight prev = null;
    for (Flight curr : travel) {
        if (prev == null) {
            prev = curr;
            continue;
        }
        if (curr.status != Status.AVAILABLE) {
            prev = curr;
            continue;
        }
        if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to)) {
            prev = curr;
            continue;
        }

        String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
        notes.add(note);
        prev = curr;
    }
    return notes;
}

Most of developers rewrite above code into:

private static List<String> indentedBusinessLogic(List<Flight> travel) {
    List<String> notes = new LinkedList<>();
    Flight prev = null;
    for (Flight curr : travel) {
        if (prev != null
                && curr.status == Status.AVAILABLE
                && curr.from.equals(prev.from)
                && curr.to.equals(prev.to)) {
            String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
            notes.add(note);
        }
        prev = curr;
    }
    return notes;
}

You can look for such kind of solutions here: http://stackoverflow.com/questions/42955096/keep-previous-element-of-collection-in-java-for-loop-even-on-continue

The problem with first piece of code is that Java for-each loop doesn't allow execution of common block of code regardless of continue statement.

But another form of for loop allows execution of block of code in case of continue statement:

private static List<String> forLoopTrick(List<Flight> travel) {
    List<String> notes = new LinkedList<>();
    Iterator<Flight> iter = travel.iterator();
    Flight curr, prev = null;

    for (; iter.hasNext(); prev = curr) {
        curr = iter.next();
        if (prev == null)
            continue;
        if (curr.status != Status.AVAILABLE)
            continue;
        if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to))
            continue;

        String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
        notes.add(note);
    }
    return notes;
}

So Java control statements in some way primitive and lack rich loop statement with finally block that executed on loop and in case of continue.

It can be emulated in two ways. With moving from sequential flow via exception:

private static class NonLocalExit extends Exception {}

private static List<String> nonLocalExitTrick(List<Flight> travel) {
    List<String> notes = new LinkedList<>();
    Flight prev = null;
    for (Flight curr : travel) {
        try {
            if (prev == null)
                throw new NonLocalExit();
            if (curr.status != Status.AVAILABLE)
                throw new NonLocalExit();
            if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to))
                throw new NonLocalExit();
            String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
            notes.add(note);
        } catch (NonLocalExit ex) { }
        prev = curr;
    }
    return notes;
}

and with moving from sequential flow via fake "do-while" loop (which is more efficient):

private static List<String> fakeLoopInside(List<Flight> travel) {
    List<String> notes = new LinkedList<>();
    Flight prev = null;
    for (Flight curr : travel) {
        do {
            if (prev == null)
                break;
            if (curr.status != Status.AVAILABLE)
                break;
            if (! curr.from.equals(prev.from) || ! curr.to.equals(prev.to))
                break;
            String note = formatNote(curr.from, curr.to, prev.datetime, curr.datetime);
            notes.add(note);
        } while (false);
        prev = curr;
    }
    return notes;
}

What you can see is that Java lacks goto! xD

You may think that workaround with using exception can be designed only by mentally ill developer.

You should know that some languages (like Elisp) have no break or continue statement. Throwing / catching an exception is only the way to mimic break and continue.

Working code example is available at http://hg.defun.work/exp/file/tip/java/error-processing-in-loop/src/ErrorChckingInLoopWithContinue.java

java, lang

Feeds

all / emacs / java

Tags

adb(1), admin(1), android(1), anki(1), ansible(2), aop(1), blog(2), bytecode(1), c(1), css(2), cygwin(2), driver(1), emacs(3), fs(1), git(3), google(1), gradle(1), hardware(1), hg(2), html(1), interview(13), java(4), js(3), lang(2), lighttpd(1), markdown(1), mobile(1), naming(1), oracle(1), print(1), problem(5), python(1), quiz(6), rst(2), security(3), spring(2), sql(2), srs(1), style(1), tls(2), txt(1), unit(1), utils(1), vcs(3), web(2), win(2), windows(1)

Archive