I have a method that can possibly throw an error. This exception is out of my control, as it is an IO exception, not a exception I am throwing. Instead of adding a throws statement to my method or fitting it in a try and catch block, would I be able to return an optional via this method?
Summary: I have a method to return a value T, except to get to that value, I have to add a try/catch block or throws statement to my method. Can I return an Optional of type T instead of throwing this error?
CodePudding user response:
"Instead of adding a throws statement to my method or fitting it in a try and catch block"
Well, those are your two options. Either you can
- Catch the exception yourself and return an empty
Optional
. This is not a good idea as it hides the exception, OR - You can let the exception propagate.
- I suppose a third option is to wrap the exception in an unchecked exception, but that just makes your interface murkier.
If you really want to defer error handling to the caller and not throw
, the ideal solution is to implement a composite object to return, containing both the error status and the return value if there is one.
CodePudding user response:
You can use code like this to wrap code that returns a value with something that captures exceptions and returns an empty optional:
import java.util.Optional;
import java.util.function.Supplier;
@FunctionalInterface
public interface ThrowingSupplier<T> extends Supplier<Optional<T>> {
@Override
default Optional<T> get() {
return getCapturingExceptions(this);
}
T getOrThrow() throws Exception;
static <U> Optional<U> getCapturingExceptions(ThrowingSupplier<U> supplier) {
try {
return Optional.ofNullable(supplier.getOrThrow());
} catch (Exception e) {
e.printStackTrace();
return Optional.empty();
}
}
}
Examples of use:
import org.junit.Test;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
public class ThrowingSupplierTest {
@Test
public void testSuccessfulSupplier() {
assertEquals(
Optional.of("Successful"),
ThrowingSupplier.getCapturingExceptions(() -> "Successful")
);
}
@Test
public void testThrowingSupplier() {
assertEquals(
Optional.empty(),
ThrowingSupplier.getCapturingExceptions(() -> {
throw new Exception("Expected failure; will be logged but test should pass");
})
);
}
}
This has all of the disadvantages outlined in Jim Garrison's answer, and I would agree with him that it is usually better to allow exceptions to propagate (wrapping in an unchecked exception when necessary). Still, this is sometimes useful. For example, if you need to implement an interface that doesn't allow for a checked exception, and allowing an unchecked exception to propagate up the call stack would have undesirable effect such as killing a thread that doesn't handle them properly. To that end, this example implements Supplier
(which cannot throw an unchecked exception) and can be used anywhere that interface is expected.
A few changes you might want to make:
This logs caught exceptions using
e.printStackTrace()
. It is important not to completely lose the exception message and stack trace, but if your application uses a logging framework, you might prefer to log the exception using that.It uses
Optional.ofNullable
on the assumption that the wrapped code might returnnull
, which isn't allowed in anOptional
. This means that it might return an emptyOptional
even if no exception occurred. If the original code is not expected to returnnull
, this could be changed toOptional.of
. However if the wrapped code does returnnull
in that case,Optional.of
will throw aNullPointerException
, which will be caught by thecatch
block and result in an emptyOptional
returned regardless.
It should also be noted that if the wrapped code returns an Optional
type itself, this will be wrapped in another Optional
layer. Getting the underlying value will require unwrapping it twice.