Home > OS >  How can I return an optional instead of throwing an exception?
How can I return an optional instead of throwing an exception?

Time:02-11

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:

  1. 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.

  2. It uses Optional.ofNullable on the assumption that the wrapped code might return null, which isn't allowed in an Optional. This means that it might return an empty Optional even if no exception occurred. If the original code is not expected to return null, this could be changed to Optional.of. However if the wrapped code does return null in that case, Optional.of will throw a NullPointerException, which will be caught by the catch block and result in an empty Optional 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.

  • Related