返回第一个非空值
我有很多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; }