2017-10-18 26 views
3

C'est une émanation de mon autre question: How to chain Optional#ifPresent() in lambda without nesting?Comment chaîner lambdas avec toutes les valeurs optionnelles disponibles dans la portée la plus interne sans imbrication Facultatif # ifPresent()?

Cependant, le problème est de savoir comment fournir une solution lambda où toutes les valeurs sont disponibles en option à la portée la plus intime

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

// Value from procA() is not available. 

Mon originale Code était:

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

      return; 
     }); 
    }); 

    throw new SomeException(); 
} 

Je comprends que le return au plus interne est portée erronée. Le nouvel exemple flatMap illustre le comportement correct.

J'utilise ifPresent() au lieu de get() pour éviter des exceptions d'exécution potentielles où je pourrais ne pas vérifier si la valeur d'un isPresent() facultatif.

+0

Ils sont disponibles. Vous pouvez faire quelque chose avec a et b. Quel est le problème? – Oleg

+0

'a' n'est pas disponible avec la méthode' flatMap'. Je veux éviter d'imbriquer 'ifPresent()' de telle sorte que 'a' et' b' soient tous deux disponibles dans la portée la plus interne et qu'une exception puisse être levée si l'une des imbrications échoue. – Zhro

Répondre

0

Je trouve cette question très intéressante car les appels chaînés avec potentiel null retours sont une nuisance commune, et facultatif peut raccourcir la chaîne de vérification null habituelle beaucoup. Mais le problème est que la nature des méthodes de flux fonctionnels cache les valeurs intermédiaires dans les fonctions de mappage. L'imbrication est un moyen de les garder disponibles, mais peut aussi devenir ennuyeux si la longueur de la chaîne d'appel augmente, comme vous l'avez compris.

Je ne peux pas penser à une solution simple et légère, mais si la nature de votre projet conduit à ces situations régulièrement, cette classe util pourrait aider:

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

Vous pouvez l'utiliser en enroulant une option ou une plaine valeur. Lorsque vous utilisez ensuite la méthode map pour chaîner des appels de méthode, il fournira un nouveau ChainedOptional tout en stockant la valeur actuelle dans une liste. À la fin (ifPresent, orElseThrow), vous obtiendrez non seulement la dernière valeur, mais aussi la liste de toutes les valeurs intermédiaires. Comme on ne sait pas combien d'appels seront enchaînés, je n'ai pas trouvé le moyen de stocker ces valeurs de manière sécurisée.

Voir exemples ici:

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. Faites-moi savoir si vous venez avec une meilleure solution.