0

How do I return a value from a lambda within the method I want to return on?

Originally, I had this:

myCollection.forEach(item -> {
  try {
    doSomething(item);
  } catch (Exception e) {
    return "There was a problem doing something: " + e.getMessage();
  }
});

But my intent was to return on the method containing all this code, not return on just the lambda. So, I ended up having to do this:

String error = "";
myCollection.stream().filter(item -> {
  try {
    doSomething(item);
    return true;
  } catch (Exception e) {
    error = "There was a problem doing something: " + e.getMessage();
  }
  return false;
});
if (!error.isEmpty()) {
  return error;
}

But this can't be the best way. What's the Java 8 functional, elegant way to do this?

Andrew Cheong
  • 29,362
  • 15
  • 90
  • 145
  • What is it you're really trying to do? – Kayaman Mar 26 '18 at 16:55
  • Do you use filtered stream after that? BTW you can't assign a value from lambda expression to external variable: `error = "There was a problem doing something: " + e.getMessage();` – statut Mar 26 '18 at 16:58
  • Given a collection of Strings (URLs), I need to run each through a third party URL verifier. Unfortunately this verifier only raises errors by throwing an exception (as opposed to returning `false` with a retrievable error message). Given an exception, I need to return a javaslang `Validation.invalid(e.getMessage())`. – Andrew Cheong Mar 26 '18 at 16:59
  • Your _I ended up having to do this_ cannot be true. `filter` is not a terminal operation and assigning a value to a captured variable is not allowed. Please post realistic code and explain what you think is wrong with it. – Sotirios Delimanolis Mar 26 '18 at 17:09
  • @SotiriosDelimanolis - I mean, the first example never worked to begin with. I meant to say, "This was the best I could come up with." But you're right, I should have added a `findFirst()` to terminate that line. In any case, the bigger problem was: returning on wrong level. – Andrew Cheong Mar 26 '18 at 17:16
  • I think you should consider not to use a lambda for the job. In this case a "for-each" `for` on the collection would be more appropriate. Is the case what you are presenting is a distilled version of your actual code and that you really need to use a lambda here? If not, use a "for-each" `for` – Valentin Ruano Mar 27 '18 at 17:18

4 Answers4

1

Anything thrown from inside of the stream can be caught with a try clause surrounding the stream.

try {
    myCollection.forEach(SurroundingClass::doSomething);
} catch (Exception e) {
    return "..."
}

However, I do not recommend using exceptions as a way of flow control. Throwing and catching exceptions are slow. You should always check whether your data will produce an exception or not before passing it to doSomething if you can.

If you are talking about checked exceptions, there is no easy way to handle them. Check out this post if you want to know about some (pretty lengthy) workarounds.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I agree, it's not the ideal way I wish to go. But unfortunately I don't have control over `doSomething()` as it's provided by a third party. I didn't know exceptions can be handled outside! Thank you. – Andrew Cheong Mar 26 '18 at 17:00
  • Exceptions _can't_ be handled outside like this with normal streams. This doesn't work. – Louis Wasserman Mar 26 '18 at 17:01
  • @LouisWasserman This is not a stream. See this: https://ideone.com/NsXqKX – Sweeper Mar 26 '18 at 17:02
  • Hm, I don't know how to formulate my code like this. I simplified a part out thinking nothing of it. I'm actually acting on `item.url()` for each `item`, so I don't think I can pass in a method reference alone... – Andrew Cheong Mar 26 '18 at 17:05
  • @AndrewCheong you can then `map` each item and then `forEach` it. – Sweeper Mar 26 '18 at 17:07
  • Thank you, but actually now it's still complaining that the exception is not handled... The highlighted segment is `SurroundingClass::doSomething`; says its exception isn't being handled. – Andrew Cheong Mar 26 '18 at 17:13
  • @AndrewCheong Oh so the exception is checked! You didn't say that in the question so I was confused. If that's the case, you can only do it the way you do it now, unfortunately... – Sweeper Mar 26 '18 at 17:18
  • I'm coming from 10 years of C++ haha... this is my first time hearing about "checked" exceptions. Man, do I have a long way to go... thank you all for your help. If you edit your answer to say, given checked exceptions, my way is the only way, then I will accept it. – Andrew Cheong Mar 26 '18 at 17:20
  • @AndrewCheong Check out this post: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8-streams it shows some not-quite-elegant ways to work around this. – Sweeper Mar 26 '18 at 17:58
1

Maybe something like this, I haven't tested.

   myCollection.stream().map(item -> {
      try {
        doSomething(item);
        return null;
      } catch (Exception e) {
        return "There was a problem doing something: " + e.getMessage();
      }
    }).filter(exp -> exp!=null).findFirst();
grape_mao
  • 1,153
  • 1
  • 8
  • 16
1

Do you really need to use a Lambda here? The best Java way would be just use 1.5 for-each on the collection:

try {
  for (final E item : myCollection) {
     doSomething(item)
  }
} catch (final Exception ex) {
  return "blah";
}

Notice that one pays a performance penalty when entering and exiting the try-catch block so is better to have it outside the loop.

Valentin Ruano
  • 2,726
  • 19
  • 29
0

Handling of checked exceptions in lambdas can be achieved in following way

    @FunctionalInterface
    public interface ThrowingConsumer<T, E extends Exception> {
        void accept(T t) throws E;
    }

    private static <T> Consumer<T> wrap(ThrowingConsumer<T, Exception> throwingConsumer) {
        return item -> {
            try {
                throwingConsumer.accept(item);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        };
    }

    private String processCollection(List</* whatever type */> myCollection) {
        try {
            myCollection.forEach(wrap(this::doSomething));
        } catch (Exception e) {
            return e.getMessage(); // error
        }
        return null; // no error
    }
Nikolai Shevchenko
  • 7,083
  • 8
  • 33
  • 42