Optional.ifAbsentThrow ()?

Suppose I need to find the value specific order , then get its id , and then its localized-id . And if I cannot do this, I want to throw an 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 that I cannot get the localized identifier, but not why.

I am missing 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 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 as follows:

 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? Is optional really missing functionality, or should I not do this for some reason?
  • 2) Is there a better, recommended way to throw more detailed exceptions for missing values?

Edit: Now it seems to me that Optional.ifAbsentThrow does not exist, because it will be a way to deal with null values, and Optional is all about not using null values โ€‹โ€‹in the first place. Optional clearly poorly reproduced with null values, it becomes verbose if you mix them. However, in the real world, itโ€™s hard for me to cope with this all-or-nothing sentence: some code is translated into options, while the other remains using values โ€‹โ€‹with a zero value. To help me mix them and refactor nullables in Optionals only when necessary, I believe that I will use the GetNonNull class below, based on the knowledge gained from @Alex and @Holgers answers on this page.

+6
source share
4 answers

Based on the knowledge gained in the answers of @Alex and @Holgers on this page, I developed the following GetNonNull -oriented class:

 Optional<Value> value = values.stream() .filter(value -> value.getOrder("order") == order) .findAny(); return GetNonNull .mapOrThrow(value, () -> new RuntimeException("Got no value.")) .mapOrThrow(Value::getAttribute, () -> new RuntimeException("Got value, but no attribute.")) .mapOrThrow(Attribute::getId, () -> new RuntimeException("Got value and attribute, but no id.")) .mapOrThrow(Id::getLocalizedId, () -> new RuntimeException("Got value, attribute and id, but no localized id.")) .get(); 

I believe that this functional code is very easy to read, and it concentrates all the exception handling code (no need to add orElseThrow at the end of the stream). The name GetNonNull means that the final result will never be zero.

Compare it with alternative imperative code:

 if (valor == null) throw new RuntimeException("Got no value."); Attribute attribute = valor.getAttribute(); if (attribute == null) throw new RuntimeException("Got value, but no attribute."); Id id = attribute.getId(); if (id == null) throw new RuntimeException("Got value and attribute, but no id."); String localizedId = id.getLocalizedId(); if (localizedId == null) throw new RuntimeException("Got value, attribute and id, but no localized id."); return localizedId; 

Instead of throwing, you can also return Optional :

 return GetNonNull .mapOrThrow(value, () -> new RuntimeException("Got no value.")) .mapOrThrow(Value::getAttribute, () -> new RuntimeException("Got value, but no attribute.")) .mapOrThrow(Attribute::getId, () -> new RuntimeException("Got value and attribute, but no id.")) .getOptional(Id::getLocalizedId); // Changed here. 

Or you can return a non- null default value:

 return GetNonNull .mapOrThrow(value, () -> new RuntimeException("Got no value.")) .mapOrThrow(Value::getAttribute, () -> new RuntimeException("Got value, but no attribute.")) .mapOrThrow(Attribute::getId, () -> new RuntimeException("Got value and attribute, but no id.")) .getOrDefault(Id::getLocalizedId, "DEFAULT"); // Changed here. 

In addition, Optional / null agnostic, i.e. nothing changes if the initial value is the correct NULL value, instead of Optional :

 Value value = ...; // Not an Optional. return GetNonNull .mapOrThrow(value, () -> new RuntimeException("Got no value.")) .mapOrThrow(Value::getAttribute, () -> new RuntimeException("Got value, but no attribute.")) .mapOrThrow(Attribute::getId, () -> new RuntimeException("Got value and attribute, but no id.")) .mapOrThrow(Id::getLocalizedId, () -> new RuntimeException("Got value, attribute and id, but no localized id.")) .get(); 

You can also use it as a simple functional idiom default value. It:

 Value value = ...; if (value != null) return value; else if (default != null) return default; else throw new NullPointerExeption(); 

It can be written as:

 // Shorter and more readable then Optional.ofNullable(value).orElse(default). // Also not the same, because here a NullPointerException is raised if default is null. return GetNonNull.getOrDefault(value, default); 

And this:

 Optional<Value> value = ...; if (value.isPresent()) return value; else if (default != null) return default; else throw new NullPointerExeption(); 

The same could also be written:

 return GetNonNull.getOrDefault(value, default); 

Since the GetNonNull class GetNonNull compatible with both common values โ€‹โ€‹and options, if some inherited imperative code that uses values โ€‹โ€‹with a null value receives later refactoring to use the options, using GetNonNull not required.

Here he is:

 public final class GetNonNull<T, E extends Throwable> { private final T value; private final E failure; private GetNonNull(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> Optional<R> getOptional(Function<T, R> f) throws E { if (failure != null) throw failure; if (value != null) { R result = f.apply(value); return Optional.ofNullable(result); } return Optional.empty(); } public static <R> R getOrDefault(R o1, Supplier<R> supplier) { if (o1 != null) return o1; R result = supplier.get(); if (result != null) return result; else throw new NullPointerException(); } public static <R> R getOrDefault(R o1, R o2) { if (o1 != null) return o1; else if (o2 != null) return o2; else throw new NullPointerException(); } public static <R> R getOrDefault(Optional<R> o1, R o2) { if (o1.isPresent()) return o1.get(); else if (o2 != null) return o2; else throw new NullPointerException(); } public <R> R getOrDefault(Function<T, R> f, R o) throws E { if (failure != null) throw failure; if (value != null) { R result = f.apply(value); if (result != null) return result; else return o; } return o; } public <R> GetNonNull<R, E> mapOrThrow(Function<T, R> f, Supplier<E> s) { if (value != null) { R result = f.apply(value); return new GetNonNull<>(result, (result != null) ? null : s.get()); } return (GetNonNull)this; } public static <T, E extends Throwable> GetNonNull<T, E> getOrThrow(Optional<T> o, Supplier<E> s) { return o.map(t -> new GetNonNull<>(t, (E)null)).orElseGet(() -> new GetNonNull<>(null, s.get())); } public static <T, E extends Throwable> GetNonNull<T, E> mapOrThrow(T o, Supplier<E> s) { return getOrThrow(Optional.ofNullable(o), s); } } 
+2
source

Optional intended to encapsulate a possibly missing value. If you perform an operation like ifAbsentThrow , it makes no sense to transfer the value as Optional , as you already know, its absence is absent during normal completion. Thus, orElseThrow does what you intend, but returns a regular object, since it is not optional.

Of course, you can apply use the function for a regular object and wrap its result in Optional again, as Alex suggested , but ask yourself: this is really an improvement in direct 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 useful method that provides the operation of applying the function and metaling accordingly, if the returned function is 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 will be:

 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, compiler type inference here exceeded it)

The last resort would be to create an Optional , which not only carries a possibly missing value, but also an additional 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 can program 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(); 
+10
source
  • Yes, Optional does not have an ifAbsentThrow method that returns Optional , if present. The closest is orElseThrow , which returns a value from optional.

  • Since your method actually doesn't work, by far the best way to do this.

This does not work because it is an 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 does not use Predicate if it is not there, so your filter does nothing.

One way to do this is to use orElseThrow and then re-wrap the result after applying your matching function using 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 can also break it down into separate statements if you think it will be more readable.

Another way would be to change Attribute#getId and Id#getName to return Optional instead of null. Then it will 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 because you do not need to re-wrap the return values โ€‹โ€‹with ofNullable , and this allows other people calling these methods to know that the return values โ€‹โ€‹are optional, but if you cannot change them, the first way will work fine.

+6
source

The hypothetical ifAbsentThrow(...) method can be expressed by existing Optional methods as follows:

 .map(Optional::of).orElseThrow(...) 

Then your original example will look like this:

 return values.stream() .filter(value -> value.getOrder("order") == order) .findAny() .map(Optional::of) .orElseThrow(() -> new RuntimeException("Could not find value of order " + order)) .map(Attribute::getId) .map(Optional::of) .orElseThrow(() -> 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)); 
+6
source

Source: https://habr.com/ru/post/981847/


All Articles