如何在没有嵌套的情况下链接具有在最内层范围可用的所有可选值的lambdas可选#ifPresent()?

问题描述:

这是我的其他问题的一个分支:How to chain Optional#ifPresent() in lambda without nesting?如何在没有嵌套的情况下链接具有在最内层范围可用的所有可选值的lambdas可选#ifPresent()?

然而,现在的问题是如何提供一个lambda解决方案,所有可选的值可在最里面的范围:

B b = procA().flatMap(this::procB).orElseThrow(SomeException::new); 

// Value from procA() is not available. 

我原来的代码为:

void SomeMethod() { 
    procA().ifPresent(a -> { 
     procB(a).ifPresent(b -> { 
      // Do something with a and b 

      return; 
     }); 
    }); 

    throw new SomeException(); 
} 

据我所知,在return最里面的范围是错误的。新的flatMap示例说明了正确的行为。

我使用ifPresent()而不是get()来避免潜在的运行时异常,因为我可能无法检查可选的isPresent()的值是否值。

+0

它们可用。你可以用a和b做些事情。问题是什么? – Oleg

+0

'a'不能使用'flatMap'方法。我想避免在最内层的作用域嵌套'if'和'b',并且在嵌套失败时抛出异常。 – Zhro

我觉得这个问题非常有趣,因为链接调用潜在的null返回是一个常见的麻烦,可选可以缩短通常的空检查链很多。但问题在于功能流方法的本质隐藏了映射函数中的中间值。嵌套是保持它们可用的一种方式,但如果调用链的长度增长,也可能会变得烦人,正如您已经意识到的那样。

我想不出一个简单而轻便的解决方案,但如果你的项目的性质导致经常这些情况下,这个UTIL类可以帮助:

public static class ChainedOptional<T> 
{ 
    private final List<Object> intermediates; 

    private final Optional<T> delegate; 

    private ChainedOptional(List<Object> previousValues, Optional<T> delegate) 
    { 
     this.intermediates = new ArrayList<>(previousValues); 
     intermediates.add(delegate.orElse(null)); 
     this.delegate = delegate; 
    } 

    public static <T> ChainedOptional<T> of(T value) 
    { 
     return of(Optional.ofNullable(value)); 
    } 

    public static <T> ChainedOptional<T> of(Optional<T> delegate) 
    { 
     return new ChainedOptional<>(new ArrayList<>(), delegate); 
    } 

    public <R> ChainedOptional<R> map(Function<T, R> mapper) 
    { 
     return new ChainedOptional<>(intermediates, delegate.map(mapper)); 
    } 

    public ChainedOptional<T> ifPresent(Consumer<T> consumer) 
    { 
     delegate.ifPresent(consumer); 
     return this; 
    } 

    public ChainedOptional<T> ifPresent(BiConsumer<List<Object>, T> consumer) 
    { 
     delegate.ifPresent(value -> consumer.accept(intermediates, value)); 
     return this; 
    } 

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 
     throws X 
    { 
     return delegate.orElseThrow(exceptionSupplier); 
    } 

    public <X extends Throwable> T orElseThrow(Function<List<Object>, X> exceptionSupplier) 
     throws X 
    { 
     return orElseThrow(() -> exceptionSupplier.apply(intermediates)); 
    } 
} 

您可以使用它通过包装可选或纯值。当您使用map方法链接方法调用时,它将提供一个新的ChainedOptional,同时将当前值存储在一个列表中。最后(ifPresent,orElseThrow),您不仅会得到最后一个值,还会列出所有中间值。由于不知道将连接多少个电话,我没有找到一种以类型安全的方式存储这些值的方法。

在这里看到的例子:

ChainedOptional.of(1) 
       .map(s -> s + 1) 
       .map(s -> "hello world") 
       .map(s -> (String) null) 
       .map(String::length) 
       .ifPresent((intermediates, result) -> { 
        System.out.println(intermediates); 
        System.out.println("Result: " + result); 
       }) 
       .orElseThrow(intermediates -> { 
        System.err.println(intermediates); 
        return new NoSuchElementException(); 
       }); 

// [1, 2, hello world, null, null] 
// Exception in thread "main" java.util.NoSuchElementException 
// at ... 

ChainedOptional.of(1) 
       .map(s -> s + 1) 
       .map(s -> "hello world") 
       // .map(s -> (String) null) 
       .map(String::length) 
       .ifPresent((intermediates, result) -> { 
        System.out.println(intermediates); 
        System.out.println("Result: " + result); 
       }) 
       .orElseThrow(intermediates -> { 
        System.err.println(intermediates); 
        return new NoSuchElementException(); 
       }); 

// [1, 2, hello world, 11] 
// Result: 11 

希望这有助于。让我知道你是否想出了一个更好的解决方案。