返回第一个非空值

时间:2017-04-07 14:07:23

标签: java lazy-evaluation coalesce

我有很多功能:

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

除默认值外,每个都可以返回一个空值。 每个功能可以采用不同的参数。例如,第一个可以不带参数,第二个可以接受一个字符串,第三个可以接受三个参数等等。我想要做的是:

ObjectUtils.firstNonNull(first(), second(), ..., default());

问题在于,由于函数调用,这是急切的评估。在哪里我想提前退出,在第二个函数之后说(因为函数调用可能很昂贵,想想API调用等)。在其他语言中,您可以执行类似的操作:

return first() || second() || ... || default()

在Java中,我知道我可以做类似的事情:

String value;
if (value = first()) == null || (value = second()) == null ...
return value;

由于所有== null检查,这不是非常易读的IMO。ObjectUtils.firstNonNull()首先创建一个集合,然后迭代,只要该函数被懒惰地评估,这是可以的。

连连呢? (除了做一堆ifs)

8 个答案:

答案 0 :(得分:33)

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

它在第一个非null值上停止,或者设置从defaultOne返回的值。只要你保持顺序,你就是安全的。当然这需要Java 8或更高版本。

第一次出现非空值时停止的原因是Stream处理每个步骤的原因。 mapintermediate operationfilter也是findFirst。另一边的short-circuiting terminal operationorElseGet。所以它继续下一个元素,直到一个匹配过滤器。如果没有元素匹配,则返回空的可选项,因此调用Supplier - 供应商。

this::first等只是方法参考。如果它们是静态的,请将其替换为YourClassName::first等。

如果您的方法的签名不同,这是一个示例:

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

请注意,https://support.google.com/cloud/contact/cloud_platform_billing仅在您调用get时进行评估。这样你就可以获得懒惰的评估行为。 supplier-lambda-expression中的方法参数必须是最终的或有效的最终。

答案 1 :(得分:8)

这可以通过Suppliers流来完成。

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

这样做的原因在于,尽管有外表,但整个执行都是由findFirst()驱动的,它会从filter()中提取一个项目,该项目从map()延迟提取项目,调用get() 1}}处理每一次拉动。当一个项目通过过滤器时,findFirst()将停止从流中提取,因此后续供应商将不会调用get()

虽然我个人找到声明性的Stream样式清洁器并且更具表现力,但是如果你不喜欢 使用Stream来使用Supplier s不喜欢这种风格:

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

显而易见的是,如果您从列表中耗尽选项,而不是返回Optional,您可以同样返回String,返回null(yuk),默认字符串或抛出异常

答案 2 :(得分:3)

它是不可读的,因为您正在处理一堆不相互表达任何类型连接的独立函数。当你试图将它们放在一起时,缺乏方向是显而易见的。

而是尝试

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

会不会很长?大概。但是,您正在一个对您的方法不友好的界面上应用代码。

现在,如果您可以更改界面,可以使界面更友好。一个可能的例子是让步骤为“ValueProvider”对象。

public interface ValueProvider {
    public String getValue();
}

然后你可以像

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

还有其他各种方法,但它们需要重构代码以使其更加面向对象。请记住,仅仅因为Java是面向对象的编程语言,并不意味着它总是以面向对象的方式使用。 first() ... last()方法列表非常不面向对象,因为它不会为List建模。尽管方法名称具有表现力,但List上有方法,可以轻松与for循环和Iterators等工具集成。

答案 3 :(得分:2)

如果您使用的是Java 8,则可以将这些函数调用转换为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();
}

如果您不想要通用实现并仅将其用于String,请将T替换为String

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

然后称之为:

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

P.S。 btw default是保留关键字,因此您不能将其用作方法名称:)

编辑:好的,最好的方法是返回Optional,然后你不需要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();
}

答案 4 :(得分:1)

如果要将其打包成实用程序方法,则必须将每个函数包装成延迟执行的内容。也许是这样的:

public interface Wrapper<T> {
    T call();
}

public static <T> T firstNonNull(Wrapper<T> defaultFunction, Wrapper<T>... funcs) {
    T val;
    for (Wrapper<T> func : funcs) {
       if ((val = func.call()) != null) {
           return val;
       }
    }
    return defaultFunction.call();
}

您可以使用java.util.concurrent.Callable而不是定义自己的Wrapper类,但是您必须处理声明Callable.call()被抛出的异常。

然后可以通过以下方式调用:

String value = firstNonNull(
    new Wrapper<>() { @Override public String call() { return defaultFunc(); },
    new Wrapper<>() { @Override public String call() { return first(); },
    new Wrapper<>() { @Override public String call() { return second(); },
    ...
);

在Java 8中,正如@dorukayhan指出的那样,您可以省去定义自己的Wrapper类,只需使用Supplier接口即可。此外,使用lambdas可以更清晰地完成调用:

String value = firstNonNull(
    () -> defaultFunc(),
    () -> first(),
    () -> second(),
    ...
);

你也可以(正如@Oliver Charlesworth建议的那样)使用方法引用作为lambda表达式的简写:

String value = firstNonNull(
    MyClass::defaultFunc,
    MyClass::first,
    MyClass::second,
    ...
);

我有两种想法,哪种更具可读性。

或者,您可以使用许多其他答案提出的流媒体解决方案之一。

答案 5 :(得分:0)

只需使用以下一个函数创建一个类:

class ValueCollector {
  String value;
  boolean v(String val) { this.value = val; return val == null; }
}

ValueCollector c = new ValueCollector();
if c.v(first()) || c.v(second()) ...
return c.value;

答案 6 :(得分:0)

上面的示例似乎对于仅在2个变量之间进行选择来说太长了,我会采用这样的方式(除非您有更长的变量列表可供选择):

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

答案 7 :(得分:-3)

你可以通过反思来实现这个目标:

public Object getFirstNonNull(Object target, Method... methods) {
    Object value = null;
    for (Method m : methods) {
        if ( (value = m.invoke(target)) != null) {
            break;
        }
    }
    return value;
}