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:
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.
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.
Exception: This method throws NullPointerException if there is no value present in this Optional instance.
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.
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();
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.
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 filter
s 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 Optional
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With