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); } }