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

前端 未结 1 1750
迷失自我
迷失自我 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
    {
        private final List intermediates;
    
        private final Optional  delegate;
    
        private ChainedOptional(List previousValues, Optional delegate)
        {
            this.intermediates = new ArrayList<>(previousValues);
            intermediates.add(delegate.orElse(null));
            this.delegate = delegate;
        }
    
        public static  ChainedOptional of(T value)
        {
            return of(Optional.ofNullable(value));
        }
    
        public static  ChainedOptional of(Optional delegate)
        {
            return new ChainedOptional<>(new ArrayList<>(), delegate);
        }
    
        public  ChainedOptional map(Function mapper)
        {
            return new ChainedOptional<>(intermediates, delegate.map(mapper));
        }
    
        public ChainedOptional ifPresent(Consumer consumer)
        {
            delegate.ifPresent(consumer);
            return this;
        }
    
        public ChainedOptional ifPresent(BiConsumer, T> consumer)
        {
            delegate.ifPresent(value -> consumer.accept(intermediates, value));
            return this;
        }
    
        public  T orElseThrow(Supplier exceptionSupplier)
            throws X
        {
            return delegate.orElseThrow(exceptionSupplier);
        }
    
        public  T orElseThrow(Function, 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)
    提交回复
    热议问题