Optional.ifAbsentThrow()?

Optional.ifAbsentThrow()?

假设我需要找到某个ordervalue,然后得到它的id,然后是它的localized-id。如果我做不到,我想抛出异常:

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

问题是异常不是很详细:它告诉我无法获取 localized-id,但没有说明原因。

我错过了一些允许我这样做的 Optional.ifAbsentThrow 方法:

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

为了解决这个问题,我创建了以下 ifAbsentThrow 方法:

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

我是这样使用的:

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

我的问题:

编辑: 现在看来 Optional.ifAbsentThrow 不存在,因为它是处理 null 值的一种方式,并且 Optional 是关于首先不使用 null 值。 Optional 显然不能很好地处理空值,如果混合使用它们会变得冗长。然而,在现实世界中,我发现很难处理这个全有或全无的命题:一些代码被转换为 Optionals,而其他代码仍然使用可为 null 的值。为了帮助我混合使用它们,并仅在必要时将可空值重构为可选值,我相信我将使用下面的 GetNonNull class,基于我从本页的@Alex 和@Holgers 答案中获得的知识。

  1. 是的,Optional 没有 ifAbsentThrow 方法 return 是 Optional 如果存在的话。最接近的是 orElseThrow,其中 return 是可选值。

  2. 既然你的方法实际上行不通,肯定有更好的方法。

它不起作用,因为这是 Optional#filter:

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

如您所见,如果 Predicate 不存在,它不会使用它,因此您的 filter 什么都不做。

一种方法是使用 orElseThrow,然后在使用 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));

如果您认为可读性更好,也可以将其分解为单独的语句。

另一种方法是将 Attribute#getIdId#getName 更改为 return Optionals 而不是 null。然后它看起来像这样:

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

我更喜欢这种方式,因为你不需要用 ofNullable 重新包装 return 值,它让其他调用这些方法的人知道 return 值是可选的,但如果你不能改变它们,那么第一种方法就可以了。

Optional 用于封装可能不存在的值。如果你执行像 ifAbsentThrow 这样的操作,那么将值作为 Optional 携带是没有意义的,因为你已经知道它在正常完成时不会不存在。所以 orElseThrow 做你想要的,但是 returns 一个普通的对象,因为它不再是 可选的

当然,你可以apply一个普通对象的函数,然后将其结果再次包装成Optional,如,但问问你自己这是否真的是一个改进通过简单的代码:

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;

您还可以创建一个实用程序方法,提供应用函数并在函数返回时适当抛出的操作 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);
}

使用这个方法,你的操作变成:

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

(不幸的是,编译器的类型推断在这里达到了极限)

最后的手段是创建 Optional 的替代方案,它不仅携带可能不存在的值,而且还携带可选的错误:

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

使用此 class,您可以将您的操作编码为

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

根据我从本页的@Alex 和@Holgers 回答中获得的知识, 我开发了以下 GetNonNull 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();

我觉得这个功能代码非常容易阅读,它把所有的异常处理代码集中在一起(不需要 添加 orElseThrow 到流的末尾)。 GetNonNull 名称暗示最终结果永远不会为空。

将其与替代命令式代码进行比较:

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;

除了投掷,你还可以 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.

或者您可以 return 非 null 默认值:

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.

它也是 Optional/null 不可知论者,也就是说,如果初始值是一个常规的可为 null 的值而不是 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();

您也可以将其用作简单的函数式获取默认值习惯用法。这个:

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

可以写成:

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

还有这个:

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

也可以完全一样写成:

return GetNonNull.getOrDefault(value, default);

由于 GetNonNull class 与 nullables 和 Optionals 兼容,如果某些遗留命令 使用可空值的代码稍后会重构为使用 Optionals,GetNonNull 用法不需要​​更改。

这里是:

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

假设的ifAbsentThrow(...)方法可以用Optional的现有方法表示如下:

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

那么你原来的例子就变成了:

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