Detailed Explanation of Exception Handling in Java Streams

  • 2021-07-10 19:33:16
  • OfStack

Foreword:

Stream, API and Lambda are important features of Java 8 that allow us to use more functional syntax styles. But a bigger problem in writing code is how to handle checked exceptions in lambda.

But you can't directly call an exception thrown from Lambda! But you can do a simple try-catch in Lambda and wrap the exception as an RuntimeException.


/**### Obviously, this is not 1 Plant a good way of expression ##**/ 
  /**
   * dosomething
   * @param item
   * @return
   */
  private static Object doSomething(String item) {
    System.out.println("doSomething:\t" + item);
    return item;
  }
 
  public static void main(String[] args) {
    List<String> myList = Arrays.asList("1", "2", "3", "4", "5", "6");
 
    myList.stream().map(item -> {
      try {
        return doSomething(item);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }).forEach(System.out::println);
  }

What about a better readable way?


/** Extract the function body to 1 In a separate method, and call the new method to do try-catch Deal with **/  
 private Object doSomething(String item) {
    System.out.println("doSomething:\t" + item);
    return item;
  }
 
  private Object trySomething(String item) {
    try {
      return doSomething(item);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
 
  public void map() {
    List<String> myList = Arrays.asList("1", "2", "3", "4", "5", "6");
    myList.stream().map(this::doSomething).forEach(System.out::println);
  }

RuntimeException

In many cases the catch of a few runtime exceptions uses RuntimeException and can be called inside lambda. If every call is caught with runtime exceptions, duplicate code will appear. So: Abstract it as a utility function and call it every time you need it!


// Definition 1 Check interfaces 
@FunctionalInterface
public interface CheckedFunction<T,R> {
  R apply(T t) throws Exception;
}

You can handle try-catch in this abstract interface and wrap the original exception into RuntimeException.


public static <T,R> Function<T,R> wrap(CheckedFunction<T,R> checkedFunction) {
 return t -> {
  try {
   return checkedFunction.apply(t);
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 };
}


/** Call public wrap  Perform exception handling */
public void map(){
    List<String> myList = Arrays.asList("1", "2", "3", "4", "5", "6");
    myList.stream()
      .map(wrap(item -> doSomething(item)))
      .forEach(System.out::println);
}

Either

The Either type is common in functional languages and not part 1 of Java if an exception occurs while using a stream and you do not want to stop processing the stream. Similar to the Optional type in Java, Either is a universal wrapper with two possibilities. For example, if we have 1 Either value, then this value can contain String type or Integer type Either < String,Integer > .


public class Either<L, R> {
  private final L left;
  private final R right;
  private Either(L left, R right) {
    this.left = left;
    this.right = right;
  }
  public static <L,R> Either<L,R> Left( L value) {
    return new Either(value, null);
  }
  public static <L,R> Either<L,R> Right( R value) {
    return new Either(null, value);
  }
  public Optional<L> getLeft() {
    return Optional.ofNullable(left);
  }
  public Optional<R> getRight() {
    return Optional.ofNullable(right);
  }
  public boolean isLeft() {
    return left != null;
  }
  public boolean isRight() {
    return right != null;
  }
  public <T> Optional<T> mapLeft(Function<? super L, T> mapper) {
    if (isLeft()) {
      return Optional.of(mapper.apply(left));
    }
    return Optional.empty();
  }
  public <T> Optional<T> mapRight(Function<? super R, T> mapper) {
    if (isRight()) {
      return Optional.of(mapper.apply(right));
    }
    return Optional.empty();
  }
  public String toString() {
    if (isLeft()) {
      return "Left(" + left +")";
    }
    return "Right(" + right +")";
  }
}

Let the function return Either instead of throwing 1 Exception.


// Log exceptions only 
public static <T,R> Function<T, Either> lift(CheckedFunction<T,R> function) {
 return t -> {
  try {
   return Either.Right(function.apply(t));
  } catch (Exception ex) {
   return Either.Left(ex);
  }
 };
}
 
// Logging exceptions and values 
public static <T,R> Function<T, Either> liftWithValue(CheckedFunction<T,R> function) {
 return t -> {
  try {
   return Either.Right(function.apply(t));
  } catch (Exception ex) {
   return Either.Left(Pair.of(ex,t));
  }
 };
}


/** Call Either.lift  Catch exception and continue execution */
public void map(){
    List<String> myList = Arrays.asList("1", "2", "3", "4", "5", "6");
    myList.stream()
      .map(Either.lift(item -> doSomething(item)))
      .forEach(System.out::println);
}

Summary:

If you want to call it checkedException in Lambda, you can wrap it as an RuntimeException. It is recommended that you create an abstraction to invoke so that you do not use try/catch every time. You can also use Either or other types to wrap the results of a function so that the flow does not terminate.


Related articles: