返回第一个非空值

我有很多function:

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

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

 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)

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

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

它在第一次出现非空值时停止的原因是Stream处理每个步骤的原因。 map是中间操作 , filter 。 另一方面, findFirst是一个短路终端操作 。 所以它继续下一个元素,直到一个匹配filter。 如果没有元素匹配,则返回空的可选项,因此调用orElseGet -supplier。

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

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

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

请注意,只有在您调用get时才会评估Supplier 。 这样你就会得到懒惰的评估行为。 supplier-lambda-expression中的方法参数必须是最终的或有效的最终。

这可以通过Suppliers流非常干净地完成。

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

这样做的原因是尽管出现了,整个执行都是由findFirst()驱动的,它从filter()拉出一个项目,它从map()拉出项目,调用get()来处理每个pull。 当一个项目通过filter时, findFirst()将停止从流中拉出,因此后续供应商将不会调用get()

虽然我个人认为声明性的Stream风格更清洁,更具表现力,但如果您不喜欢这种风格,则不必使用Stream与Supplier

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

显而易见的是,如果你从列表中耗尽选项,那么你可以返回一个String ,返回null(yuk),默认字符串或抛出exception。

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

相反,试试

  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 providers) { String value; for (ValueProvider provider : providers) { value = provider.getValue(); if (value != null) return value; } return null; } 

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

如果您使用的是Java 8,则可以将这些函数调用转换为lambdas。

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

如果您不想要generics实现并仅将其用于String请继续使用String替换T

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

然后称之为:

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

PS btw default是保留关键字,所以你不能将它用作方法名:)

编辑:好的,最好的方法是返回Optional,然后你不需要separetely传递默认供应商:

 @SafeVarargs public static Optional firstNonNull(Supplier... funcs){ return Arrays.stream(funcs).filter(p -> p.get() != null).map(s -> s.get()).findFirst(); } 

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

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

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

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

 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, ... ); 

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

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

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

 class ValueCollector { String value; boolean v(String val) { this.value = val; return val == null; } } ValueCollector c = new ValueCollector(); if cv(first()) || cv(second()) ... return c.value; 

你可以通过反思完成这个:

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