Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional.ifAbsentThrow()?

Suppose I need to find a value of a certain order, then get its id, and then its localized-id. And if I can't do it, I want to throw and exception:

return values.stream()
             .filter(value -> value.getOrder("order") == order)
             .findAny()
             .map(Attribute::getId)
             .map(Id::getLocalizedId)
             .orElseThrow(() -> new RuntimeException("Could not get the localized id of the value of order " + order));

The problem is that the exception is not very detailed: it tells me I can't get the localized-id, but not why.

I miss some Optional.ifAbsentThrow method that would allow me to do this:

return values.stream()
             .filter(value -> value.getOrder("order") == order)
             .findAny()
             .ifAbsentThrow(() -> new RuntimeException("Could not find value of order " + order));
             .map(Attribute::getId)
             .ifAbsentThrow(() -> new RuntimeException("Value of order " + order + " has no id"));
             .map(Id::getLocalizedId)
             .orElseThrow(() -> new RuntimeException("Could get the id but not the localized id of the value of order " + order));

To solve this problem, I have created the following ifAbsentThrow method:

public static <T, X extends RuntimeException> Predicate<T> ifAbsentThrow(Supplier<? extends X> exceptionSupplier) throws RuntimeException {
    return valor -> { 
                    if (valor == null) throw exceptionSupplier.get();
                    return true; 
                    };
    }

And I use it like this:

return values.stream()
             .filter(value -> value.getOrder("order") == order)
             .findAny()
             .filter(ifAbsentThrow(() -> new RuntimeException("Could not find value of order " + order));
             .map(Attribute::getId)
             .filter(ifAbsentThrow(() -> new RuntimeException("Value of order " + order + " has no id"));
             .map(Id::getLocalizedId)
             .orElseThrow(() -> new RuntimeException("Could get the id but not the localized id of the value of order " + order));

My Questions:

  • 1) Am I missing something here? Is Optional really missing this functionality or I shouldn't be doing this for some reason?
  • 2) Is there a better, recommended way of throwing more detailed exceptions for missing values?

Edit: It now seems to me that Optional.ifAbsentThrow does not exist because it would be a way of dealing with null values, and Optional is all about not using null values in the first place. Optional clearly doesn't play well with null values, it gets verbose if you mix them. However, in the real world I find it difficult to deal with this all-or-nothing proposition: Some code gets translated to Optionals, while other remains using nullable values. To help me mix them, and refactor nullables to Optionals only when necessary, I believe I will be using the GetNonNull class below, built upon knowledge I gained from @Alex and @Holgers answers in this page.

like image 616
MarcG Avatar asked Feb 01 '15 22:02

MarcG


People also ask

What does Optional class return if value is absent?

It returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional. If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException.

What exception is thrown by Optional get ()?

Exception: This method throws NullPointerException if there is no value present in this Optional instance.

Which exception is thrown by Optional get () when not nested within Optional isPresent ()?

NoSuchElementException Exception Via orElseThrow() Since Java 10. Using the Optional. orElseThrow() method represents another elegant alternative to the isPresent()-get() pair. Sometimes, when an Optional value is not present, all you want to do is to throw a java.


2 Answers

Optional is meant to encapsulate a possibly absent value. If you perform an operation like ifAbsentThrow there is no point in carrying the value as an Optional as you already know it’s not absent on normal completion. So orElseThrow does what you intend but returns an ordinary object as it’s not optional anymore.

Of course, you can apply a function to an ordinary object and wrap its result into an Optional again, as Alex suggested, but ask yourself whether this is really an improvement over the straight-forward code:

Attribute a=values.stream().filter(value -> value.getOrder("order") == order).findAny()
    .orElseThrow(() -> new RuntimeException("Could not find value of order " + order));
Id id=a.getId();
if(id==null)
    throw new RuntimeException("Value of order " + order + " has no id");
String name=id.getName();
if(name==null)
    throw new RuntimeException(
        "Could get the id but not the localized id of the value of order " + order);
return name;

You can also create a utility method providing the operation of applying a function and throwing appropriately if the function returned null:

static <T,R, E extends Throwable> R get(T o, Function<T,R> f, Supplier<E> s) throws E {
    return Optional.ofNullable(f.apply(o)).orElseThrow(s);
}

using this method, your operation becomes:

return get(ContainingClass.<Attribute,Id,RuntimeException>get(
 values.stream().filter(value -> value.getOrder("order") == order).findAny()
   .orElseThrow(  () -> new RuntimeException("Could not find value of order " + order)),
 Attribute::getId,() -> new RuntimeException("Value of order " + order + " has no id")),
 Id::getName,     () -> new RuntimeException(
     "Could get the id but not the localized id of the value of order " + order));

(Unfortunately, the type inference of the compiler hit its limit here)

The last resort would be the creation of an alternative to Optional which does not only carry a possibly absent value but an optional error:

public final class Failable<T,E extends Throwable> {
    private final T value;
    private final E failure;
    private Failable(T value, E failure) {
        this.value=value;
        this.failure=failure;
        if(value==null && failure==null) throw new NullPointerException();
    }
    public T get() throws E {
        if(failure!=null) throw failure;
        return value;
    }
    public <R> Failable<R,E> map(Function<T,R> f, Supplier<E> s) {
        if(value!=null) {
            R result=f.apply(value);
            return new Failable<>(result, result!=null? null: s.get());
        }
        // already failed, types of R and T are irrelevant
        @SuppressWarnings("unchecked") Failable<R,E> f0=(Failable)this;
        return f0;
    }
    public static <T,E extends Throwable> Failable<T,E> of(Optional<T> o, Supplier<E> s) {
        return o.map(t -> new Failable<>(t, (E)null))
                .orElseGet(()->new Failable<>(null, s.get()));
    }
}

Using this class, you could code your operation as

return Failable.of(
      values.stream().filter(value -> value.getOrder("order") == order).findAny(),
      () -> new RuntimeException("Could not find value of order " + order))
   .map(Attribute::getId, ()->new RuntimeException("Value of order "+order+" has no id"))
   .map(Id::getName, ()->new RuntimeException(
        "Could get the id but not the localized id of the value of order " + order)).get();
like image 60
Holger Avatar answered Oct 19 '22 13:10

Holger


  1. Yes, Optional does not have an ifAbsentThrow method which returns the Optional if it is present. The closest is orElseThrow which returns the value from the optional.

  2. Since your way doesn't actually work, there certainly is a better way to do it.

It doesn't work because this is the implementation of Optional#filter:

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

As you can see, it doesn't use the Predicate if it is not present, so your filters are doing nothing.

One way you could do it is to use orElseThrow and then re-wrap the result after applying your mapping function with ofNullable:

Optional<Attribute> o = //get your first optional from the stream.
return Optional.ofNullable(Optional.ofNullable(
        o.orElseThrow(() -> new RuntimeException("Could not find value of order " + order))
        .getId())
        .orElseThrow(() -> new RuntimeException("Value of order " + order + " has no id"))
        .getName())
        .orElseThrow(() -> new RuntimeException("Could get the id but not the localized id of the value of order " + order));

You could also break that up into separate statements if you think that would be more readable.

Another way would be to change Attribute#getId and Id#getName to return Optionals instead of null. Then it would look like this:

return values.stream()
        .filter(value -> value.getOrder("order") == order)
        .findAny()
        .orElseThrow(() -> new RuntimeException("Could not find value of order " + order))
        .getId()
        .orElseThrow(() -> new RuntimeException("Value of order " + order + " has no id"))
        .getName()
        .orElseThrow(() -> new RuntimeException("Could get the id but not the localized id of the value of order " + order));

I would prefer this way because you don't need to re-wrap the return values with ofNullable and it lets other people calling those methods know that the return values are optional, but if you can't change them then the first way would work fine.

like image 29
Alex - GlassEditor.com Avatar answered Oct 19 '22 12:10

Alex - GlassEditor.com