Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstracting try/catch with Function in Java 8

I've recently found myself writing many blocks of the following form:

try {
    return Optional.of(thing.doSomething(arg));
} catch (Exception e) {
    System.out.println(e.getMessage());
    return Optional.empty();
}

This is necessary because certain methods signal that they may throw exceptions, and Eclipse yells at me if I don't surround those methods with try/catch blocks.

So I wrote this:

public static <A, T> Optional<T> tryOpt (Function<A, T> f, A arg) {
    try {
        return Optional.of(f.apply(arg));
    } catch (Exception e) {
        System.out.println(e.getMessage());
        return Optional.empty();
    }
}

So any Function I pass to tryOpt is enclosed in a try/catch block and safely executed, and its result returned as an Optional. But Eclipse still yells at me for using it:

return tryOpt(
          ((x) -> thing.doSomething(x)),
          arg);

My question is, is there any way for me to tell Eclipse and/or the java compiler that it's okay, and that I am indirectly enclosing the offending method within a try/catch block? Or have I just misunderstood something about how java works, in which case, would someone mind enlightening me? Or, on the other hand, can I safely ignore Eclipse's red-line warnings in this case?

like image 289
DruidGreeneyes Avatar asked Dec 25 '15 11:12

DruidGreeneyes


4 Answers

Function.apply does not allow to throw an exception, therefore the compiler complains when you pass in a lambda expression which does that.

You can resolve this issue by using a functional interface which allows to throw exceptions. (Maybe such a interface already exists in one of the java.util.* packages):

public static <A, T> Optional<T> tryOpt (Fn<A, T> f, A arg) {
    try {
        return Optional.of(f.apply(arg));
    } catch (Exception e) {
        System.out.println(e.getMessage());
        return Optional.empty();
    }
}


public static interface Fn<A, T> {
      T apply(A a) throws Exception;
}
like image 137
wero Avatar answered Oct 02 '22 09:10

wero


If it's a red line, it's not a warning, it's an error.

In this case, the Function<A,R> interface declares a method that does not throw an exception, but your implementation of it does. In this case, your best bet is to create your own functional interface that declares a method that does throw an exception, something like

public interface SupplierWithException<R> {R get();}

You use it like this:

return asOptional(() -> thing.doSomething(arg1, arg2, arg3));

However, I would suggest that simply swallowing exceptions is bad form; if you use the asOptional method a lot this indicates that you have an impedance mismatch between your sections of your code (a library that you use?). You could either rethrow the exceptions as RuntimeExceptions, or use something like RxJava that explicitly handles exceptions.

like image 45
Tassos Bassoukos Avatar answered Oct 02 '22 11:10

Tassos Bassoukos


The main problem is how lambdas mixes up with checked exception, the design went in this way which could lead to awkward solutions like wrapping each call to methods which throw inside each lambda in which they are used.

I ended up using a wrapper which manages this for me, something like:

  @FunctionalInterface
  public interface FunctionThrowing<T, R>
  {
    R apply(T t) throws Exception;
  }


  public static <T, R> Function<T, R> wrapFunction(FunctionThrowing<T, R> function)
  {
    return a -> {
      try
      {
        return function.apply(a);
      } catch (Exception exception)
      {
        throw exception;
        return null;
      }
    };
  }

This basically wraps a function by returning a function which handles the checked exception and converts it to an unchecked exception so that you don't need to wrap each invocation with a try/catch block but you still don't lose the ability to catch the exception (since it becomes unchecked).

like image 35
Jack Avatar answered Oct 02 '22 09:10

Jack


Modern way of doing it:

interface VoidCallable {
    void call() throws Exception;
}

public static void tryCatchBlockWrapper(VoidCallable func) {
    try {
        func.call();
    } catch (IOException e) {
        // do something
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void functionToWrap() {
    tryCatchBlockWrapper(() -> throw new IOException());   
}

P.S. If you want to add context (pass more arguments) you can just add them:

public static void tryCatchBlockWrapper(String anotherArg, VoidCallable func) {
    // ...
}
like image 21
AlexFreik Avatar answered Oct 02 '22 10:10

AlexFreik