How to chain lambdas with all optional values available at the innermost scope without nesting Optional#ifPresent()?

前端 未结 1 1751
迷失自我
迷失自我 2020-12-19 10:59

This is an offshoot of my other question: How to chain Optional#ifPresent() in lambda without nesting?

However, the problem now is how to provide a lambda solution w

相关标签:
1条回答
  • 2020-12-19 11:31

    I find this question very interesting as chained calls with potential null returns are a common nuisance, and Optional can shorten the usual null check chain a lot. But the issue there is that the nature of the functional stream methods hides the intermediate values in the mapping functions. Nesting is a way to keep them available, but can also get annoying if the length of the call chain grows, as you have realized.

    I cannot think of an easy and lightweight solution, but if the nature of your project leads to these situations regularly, this util class could help:

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

    You use it by wrapping an Optional or a plain value. When you then use the map method to chain method calls, it will provide a new ChainedOptional while storing the current value in a list. At the end (ifPresent, orElseThrow), you will not only get the last value, but also the list of all intermediate values. Since it is not known how many calls will be chained, I did not find a way to store those values in a type-safe way, though.

    See examples here:

    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
    

    Hope this helps. Let me know if you come up with a nicer solution.

    0 讨论(0)
提交回复
热议问题