Return first non-null value

前端 未结 8 1980
情歌与酒
情歌与酒 2020-12-14 07:17

I have a number of functions:

String first(){}
String second(){}
...
String default(){}

Each can return a null value, except the default. <

相关标签:
8条回答
  • 2020-12-14 07:50

    If you are using java 8 you can convert these function calls to lambdas.

    public static<T> T firstNonNull(Supplier<T> defaultSupplier, Supplier<T>... funcs){
        return Arrays.stream(funcs).filter(p -> p.get() != null).findFirst().orElse(defaultSupplier).get();
    }
    

    If you don't want the generic implementation and use it only for Strings go on and just replace T with String:

    public static String firstNonNull(Supplier<String> defaultSupplier, Supplier<String>... funcs){
        return Arrays.stream(funcs).filter(p -> p.get() != null).findFirst().orElse(defaultSupplier).get();
    }
    

    And then call it like:

    firstNonNull(() -> getDefault(), () -> first(arg1, arg2), () -> second(arg3));
    

    P.S. btw default is a reserved keyword, so you cannot use it as a method name :)

    EDIT: ok, the best way to do this would be to return Optional, then you don't need to pass default supplier separetely:

    @SafeVarargs
    public static<T> Optional<T> firstNonNull(Supplier<T>... funcs){
        return Arrays.stream(funcs).filter(p -> p.get() != null).map(s -> s.get()).findFirst();
    }
    
    0 讨论(0)
  • You can accomplish this via reflection:

    public Object getFirstNonNull(Object target, Method... methods) {
        Object value = null;
        for (Method m : methods) {
            if ( (value = m.invoke(target)) != null) {
                break;
            }
        }
        return value;
    }
    
    0 讨论(0)
  • 2020-12-14 07:55
    String s = Stream.<Supplier<String>>of(this::first, this::second /*, ... */)
                     .map(Supplier::get)
                     .filter(Objects::nonNull)
                     .findFirst()
                     .orElseGet(this::defaultOne);
    

    It stops on the first non-null value or else sets the value which is returned from defaultOne. As long as you stay sequential, you are safe. Of course this requires Java 8 or later.

    The reason why it stops on the first occurrence of a non-null value is due how the Stream handles each step. The map is an intermediate operation, so is filter. The findFirst on the other side is a short-circuiting terminal operation. So it continues with the next element until one matches the filter. If no element matches an empty optional is returned and so the orElseGet-supplier is called.

    this::first, etc. are just method references. If they are static replace it with YourClassName::first, etc.

    Here is an example if the signature of your methods would differ:

    String s = Stream.<Supplier<String>>of(() -> first("takesOneArgument"),
                                           () -> second("takes", 3, "arguments")
                                       /*, ... */)
                     .map(Supplier::get)
                     .filter(Objects::nonNull)
                     .findFirst()
                     .orElseGet(this::defaultOne);
    

    Note that the Supplier is only evaluated when you call get on it. That way you get your lazy evaluation behaviour. The method-parameters within your supplier-lambda-expression must be final or effectively final.

    0 讨论(0)
  • 2020-12-14 07:55

    It isn't readable because you are dealing with a bunch of separate functions that don't express any kind of connection with each other. When you attempt to put them together, the lack of direction is apparent.

    Instead try

     public String getFirstValue() {
          String value;
          value = first();
          if (value != null) return value;
          value = second();
          if (value != null) return value;
          value = third();
          if (value != null) return value;
          ...
          return value;
     }
    

    Will it be long? Probably. But you are applying code on top of a interface that's not friendly toward your approach.

    Now, if you could change the interface, you might make the interface more friendly. A possible example would be to have the steps be "ValueProvider" objects.

    public interface ValueProvider {
        public String getValue();
    }
    

    And then you could use it like

    public String getFirstValue(List<ValueProvider> providers) {
        String value;
        for (ValueProvider provider : providers) {
           value = provider.getValue();
           if (value != null) return value;
        }
        return null;
    }
    

    And there are various other approaches, but they require restructuring the code to be more object-oriented. Remember, just because Java is an Object-Oriented programming language, that doesn't mean it will always be used in an Object-Oriented manner. The first()...last() method listing is very not-object oriented, because it doesn't model a List. Even though the method names are expressive, a List has methods on it which permit easy integration with tools like for loops and Iterators.

    0 讨论(0)
  • This can be done pretty cleanly with a stream of Suppliers.

    Optional<String> result = Stream.<Supplier<String>> of(
         () -> first(), 
         () -> second(),
         () -> third() )
      .map( x -> x.get() )
      .filter( s -> s != null)
      .findFirst(); 
    

    The reason this works is that despite appearances, the whole execution is driven by findFirst(), which pulls an item from filter(), which lazily pulls items from map(), which calls get() to handle each pull. findFirst() will stop pulling from the stream when one item has passed the filter, so subsequent suppliers will not have get() called.

    Although I personally find the declarative Stream style cleaner and more expressive, you don't have to use Stream to work with Suppliers if you don't like the style:

    Optional<String> firstNonNull(List<Supplier<String>> suppliers {
        for(Supplier<String> supplier : suppliers) {
            String s = supplier.get();
            if(s != null) {
                return Optional.of(s);
            }
        }
        return Optional.empty();
    }
    

    It should be obvious how instead of returning Optional you could equally return a String, either returning null (yuk), a default string, or throwing an exception, if you exhaust options from the list.

    0 讨论(0)
  • 2020-12-14 08:05

    The above examples seemed too long for just choosing between 2 variables, I'd go with something like this (unless you've got a longer list of variables to chose from):

    Optional.ofNullable(first).orElse(Optional.ofNullable(second).orElse(default));

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