Is there an elegant way to get the first non null value of multiple method returns in Java?

前端 未结 3 760
南方客
南方客 2021-02-08 10:06

You have already seen this many times yourself, of that I\'m sure:

public SomeObject findSomeObject(Arguments args) {
    SomeObject so = queryFirstSource(args);         


        
相关标签:
3条回答
  • 2021-02-08 10:53

    It depends on some factors you are not defining. Do you have a fixed, rather small set of query…Source actions as shown in your question or are you rather heading to having a more flexible, extensible list of actions?

    In the first case you might consider changing the query…Source methods to return an Optional<SomeObject> rather than SomeObject or null. If you change your methods to be like

    Optional<SomeObject> queryFirstSource(Arguments args) {
        …
    }
    

    You can chain them this way:

    public SomeObject findSomeObject(Arguments args) {
        return queryFirstSource(args).orElseGet(
          ()->querySecondSource(args).orElseGet(
          ()->queryThirdSource(args).orElse(null)));
    }
    

    If you can’t change them or prefer them to return null you can still use the Optional class:

    public SomeObject findSomeObject(Arguments args) {
        return Optional.ofNullable(queryFirstSource(args)).orElseGet(
           ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
           ()->queryThirdSource(args)));
    }
    

    If you are looking for a more flexible way for a bigger number of possible queries, it is unavoidable to convert them to some kind of list or stream of Functions. One possible solution is:

    public SomeObject findSomeObject(Arguments args) {
        return Stream.<Function<Arguments,SomeObject>>of(
          this::queryFirstSource, this::querySecondSource, this::queryThirdSource
        ).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null);
    }
    

    This performs the desired operation, however, it will compose the necessary action every time you invoke the method. If you want to invoke this method more often, you may consider composing an operation which you can re-use:

    Function<Arguments, SomeObject> find = Stream.<Function<Arguments,SomeObject>>of(
        this::queryFirstSource, this::querySecondSource, this::queryThirdSource
    ).reduce(a->null,(f,g)->a->Optional.ofNullable(f.apply(a)).orElseGet(()->g.apply(a)));
    
    public SomeObject findSomeObject(Arguments args) {
        return find.apply(args);
    }
    

    So you see, there are more than one way. And it depends on the actual task what direction to go. Sometimes, even the simple if sequence might be appropriate.

    0 讨论(0)
  • 2021-02-08 10:55

    Yes, there is:

    Arrays.asList(source1, source2, ...)
       .stream()
       .filter(s -> s != null)
       .findFirst();
    

    This is more flexible, since it returns an Optional not null in case a not-null source is found.

    Edit: If you want lazy evaluation you should use a Supplier:

    Arrays.<Supplier<Source>>asList(sourceFactory::getSource1, sourceFactory::getSource2, ...)
       .stream()
       .filter(s -> s.get() != null)
       .findFirst();
    
    0 讨论(0)
  • 2021-02-08 11:02

    I would write it like this (you may not need generics here but why not do it):

    public static <A, T> Optional<T> findFirst(Predicate<T> predicate, A argument,
                                             Function<A, T>... functions) {
      return Arrays.stream(functions)
              .map(f -> f.apply(argument))
              .filter(predicate::test)
              .findFirst();
    }
    

    And you can call it with:

    return findFirst(Objects::nonNull, args, this::queryFirstSource,
                                           this::querySecondSource,
                                           this::queryThirdSource);
    

    (assuming your queryXXX methods are instance methods)

    The methods will be applied in order until one returns a value that matches the predicate (in the example above: returns a non null value).

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