38

Is there a shorter syntax to if/throw else/return in Java 8? java.util.Optional provides a means to accomplish this in one statement, but it requires creating an Optional instance with every call that has a non-null reference.

Can this be accomplished in a single statement?

public static MyEnum fromString(String value) {
    MyEnum result = enumMap.get(value);
    if (result == null)
        throw new IllegalArgumentException("Unsupported value: " + value);
    return result;
}

Optional Example (bad, requires Optional instance every time)

public static MyEnum fromString(String value) {
    return Optional.ofNullable(enumMap.get(value)).orElseThrow(
        () -> new IllegalArgumentException("Unsupported value: " + value));
}
Sam Berry
  • 7,394
  • 6
  • 40
  • 58
  • 4
    I would stick with your first example. It is simple, clear, and concise, and only one more line of code than the example that requires me to understand what this new `Optional` thing is. – Jonathon Reinhart Oct 22 '14 at 03:38
  • @JonathonReinhart Yeah, pragmatically I agree with you. I've been running into this frequently in a personal project of mine and it made me wonder. I'm asking out of curiosity rather then seeking technical advantage. – Sam Berry Oct 22 '14 at 03:41
  • 1
    Have you benchmarked the code with Optional? The VM does escape analysis so that it can sometimes allocate objects on the stack, which should be really quick. – Yogu Oct 22 '14 at 06:20
  • Wouldn't it be better to have `Optional`s in your `Map`? – Dirk Lachowski Oct 22 '14 at 09:14
  • 1
    @Dirk Lachowski: A `Map<…,Optional<…>>` still returns `null` for keys not present in the `Map`. – Holger Oct 22 '14 at 11:53
  • @Holger Depends on the concrete impl. You could easily create a delegate returning `Optional.empty()`s for a missing entry. But i got your point. – Dirk Lachowski Oct 22 '14 at 13:04

2 Answers2

40

The impact of a temporary Optional instance is negligible. Usually the JVM will detect its temporary nature and optimize away the instance. Even if the temporary instance creation is not optimized away, the impact of one single temporary object on the memory management is ridiculously low. See also GC overhead of Optional<T> in Java.

However, if the map is mutable, you can use the following trick:

public static MyEnum fromString(String value) {
    return enumMap.computeIfAbsent(value, v -> {
        throw new IllegalArgumentException("Unsupported value: " + v); });
}

Note that the Map is not modified by this code but still must be mutable as an immutable map might throw an UnsupportedOperation exception for the attempt to use computeIfAbsent without ever checking whether the operation would really modify the map.


But in the end, there is nothing wrong with Optional. But note that the code in your question is wrong. The lambda expression you pass to the method Optional.orElseThrow is meant to supply the desired exception, not to throw it:

public static MyEnum fromString(String value) {
    return Optional.ofNullable(enumMap.get(value)).orElseThrow(() ->
        new IllegalArgumentException("Unsupported value: " + value) // just return it
    );
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    So there is not anything built into the language, like an operator, to deal with this kind of scenario. But, it seems like good practice to offer methods like `orElseThrow` or `computeIfAbsent` through an object's interface to encapsulate if/else/throw logic, or at the least to provide a simpler means to implement. – Sam Berry Oct 23 '14 at 05:54
  • 2
    I agree, offering methods which take the fall-back action as a parameter in the higher level interfaces might be a good practice. In the best case, you can pass the supplier/function down to the lower level method, like a back-end map’s `computeIfAbsent` or optional’s `orElseThrow`. Returning an `Optional` to the caller when a method might fail is also a good choice. – Holger Oct 23 '14 at 07:50
  • Am I correct in believing that using computeIfAbsent() restricts you to only throwing unchecked exceptions? – Jeremy Apr 08 '16 at 08:30
  • 1
    @Jeremy: that’s correct. If you want to throw checked exceptions, you have to use the solution using the `Optional` (as recently discussed [here](http://stackoverflow.com/a/36482669/2711488)), which is the better solution anyway, as it also works with immutable `Map`s and is easier to understand. – Holger Apr 08 '16 at 08:45
3

If you are willing to live with NullPointerException, you can do:

public static MyEnum fromString(String value) {
    return requireNonNull(enumMap.get(value), () -> "Unsupported: " + value);
}

This assumes import static java.util.Objects.requireNonNull

Edit:

If you are really particular about what type of exception you throw, just implement your own static utility method:

static<T, X extends Throwable> T nonNullOrThrow(T val, Supplier<? extends X> exSupplier) throws X {
    if (val != null) return val;
    else throw exSupplier.get();
}

Then you'll be able to do

return nonNullOrThrow(enumMap.get(value), () -> new IllegalArgumentException("unsupported: " + k));
Misha
  • 27,433
  • 6
  • 62
  • 78
  • Dag that's so close! I'm looking for a solution that will allow for providing different exceptions. If only requireNonNull's Supplier took a Throwable instead! – Sam Berry Oct 22 '14 at 04:57
  • 2
    Note that the lambda expression for the `Supplier` captures the value used in the exception’s message, therefore a new instance of `Supplier` is created every time. That might bother people thinking that a single instance of `Optional` is a problem. If you want to avoid every instance creation, you will have to use a `Function` which will convert the value argument to an exception so it can be invariant and implemented as a singleton by the JRE. – Holger Oct 22 '14 at 11:51