返回第一个非空值
Posted
技术标签:
【中文标题】返回第一个非空值【英文标题】:Return first non-null value 【发布时间】:2017-09-02 23:04:24 【问题描述】:我有很多功能:
String first()
String second()
...
String default()
每个都可以返回一个空值,默认值除外。 每个函数可以采用不同的参数。例如,第一个可以不带参数,第二个可以带一个字符串,第三个可以带三个参数,等等。我想做的是这样的:
ObjectUtils.firstNonNull(first(), second(), ..., default());
问题在于,由于函数调用,this 会进行急切评估。在第二个函数之后说,我想提前退出哪里(因为函数调用可能很昂贵,想想 API 调用等)。在其他语言中,您可以执行类似的操作:
return first() || second() || ... || default()
在 Java 中,我知道我可以执行以下操作:
String value;
if (value = first()) == null || (value = second()) == null ...
return value;
由于所有 == null 检查,IMO 的可读性不太好。ObjectUtils.firstNonNull() 首先创建一个集合,然后进行迭代,只要函数被延迟评估就可以了。
建议? (除了做一堆ifs)
【问题讨论】:
使用方法引用来避免急切的评估? 你有java 8吗? Java 8+ 没问题default
不可能是你的方法名:P
【参考方案1】:
上面的例子似乎太长了,不能只在 2 个变量之间进行选择,我会选择这样的东西(除非你有更长的变量列表可供选择):
Optional.ofNullable(first).orElse(Optional.ofNullable(second).orElse(default));
【讨论】:
【参考方案2】:如果你想把它打包成一个实用方法,你必须把每个函数打包成一个延迟执行的东西。也许是这样的:
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
接口。此外,使用 lambda 可以更干净地完成调用:
String value = firstNonNull(
() -> defaultFunc(),
() -> first(),
() -> second(),
...
);
您还可以(正如@Oliver Charlesworth 建议的那样)使用方法引用作为 lambda 表达式的简写:
String value = firstNonNull(
MyClass::defaultFunc,
MyClass::first,
MyClass::second,
...
);
对于哪个更具可读性,我有两种看法。
或者,您可以使用许多其他答案提出的流式解决方案之一。
【讨论】:
你不能排队 ||如果函数不返回布尔值,则在 Java 中 @lorenzocastillo - D'oh。是的,第一个选项是行不通的。太多的混合 Java 和 javascript 编程。 :( 你应该能够用最后一个 sn-p 中的方法引用替换 lambdas。java.util.function.Supplier
是java.util.concurrent.Callable
的更好替代品。唯一的区别是它唯一的方法叫做“get”而不是“call”。
@dorukayhan - 好点。我将更新答案的 Java 8 部分。 (还有一个区别:Supplier.get()
没有声明抛出Exception
,所以它实际上比Callable.call()
更容易使用。)【参考方案3】:
只需创建一个具有这样一个函数的类:
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;
【讨论】:
【参考方案4】:这可以通过Suppliers
流非常干净地完成。
Optional<String> result = Stream.<Supplier<String>> of(
() -> first(),
() -> second(),
() -> third() )
.map( x -> x.get() )
.filter( s -> s != null)
.findFirst();
这样做的原因是尽管看起来,整个执行是由findFirst()
驱动的,它从filter()
中拉取一个项目,它懒惰地从map()
中拉取项目,它调用get()
来处理每个拉取。当一项通过过滤器时,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();
应该很明显,如果您用尽列表中的选项,您可以同样返回 String
,而不是返回 String
,或者返回 null (yuk)、默认字符串,或者抛出异常。
【讨论】:
【参考方案5】:String s = Stream.<Supplier<String>>of(this::first, this::second /*, ... */)
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElseGet(this::defaultOne);
它在第一个非空值上停止,否则设置从defaultOne
返回的值。只要你保持顺序,你就是安全的。当然这需要 Java 8 或更高版本。
它在第一次出现非空值时停止的原因是Stream
处理每个步骤的方式。 map
是 intermediate operation,filter
也是。另一边的findFirst
是short-circuiting terminal operation。所以它继续下一个元素,直到一个匹配过滤器。如果没有元素匹配,则返回一个空的可选项,因此调用orElseGet
-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);
请注意,Supplier
仅在您对其调用 get
时进行评估。这样你就会得到你的惰性评估行为。您的供应商 lambda 表达式中的方法参数必须是最终的或有效的最终。
【讨论】:
如果每个方法都采用不同的参数,这会是什么样子? 这是一个例子,说明使用最新的“工具”有时会造成障碍。在 C++ 中,他们最后教授了运算符重载,每个人都在不需要的地方实现它们,从而破坏了语言。这会创建大量的中间对象来处理流处理,防止添加调试语句,并且并不比简单的 for 循环更容易阅读。别误会,我喜欢 Lambda,但如果我想用 Lambda 编写所有东西,我会使用 Lisp。 @EdwinBuck 为什么会有障碍?这是 Stream 用于其擅长的领域的一个干净示例。 @lorenzocastillo “如果每个方法都采用不同的参数,这会是什么样子?”在我看来,你应该澄清你想要在问题中得到这个答案。请对其进行编辑,以使答案有效,并且您的两个问题都能找到答案。 @AndriyKryvtsun 我们想要惰性评估,供应商确保评估仅在我们调用get
时进行。如果你写Stream.of(first(), second())
,那么这两个方法都将在Stream
被构造之前执行。【参考方案6】:
如果您使用的是 java 8,则可以将这些函数调用转换为 lambda。
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
s,请继续将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));
附: btw default
是保留关键字,因此不能将其用作方法名称:)
编辑:好的,最好的方法是返回 Optional,然后你不需要单独传递默认供应商:
@SafeVarargs
public static<T> Optional<T> firstNonNull(Supplier<T>... funcs)
return Arrays.stream(funcs).filter(p -> p.get() != null).map(s -> s.get()).findFirst();
【讨论】:
【参考方案7】:它不可读,因为您正在处理一堆单独的函数,这些函数彼此之间没有任何形式的联系。当您尝试将它们放在一起时,显然缺乏方向。
不如试试
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
等工具集成。
【讨论】:
ValueProvider
的另一个合适的名称是 Supplier
。或者您可以使用 JRE 中已经存在的那个。 Supplier<String>
@slim 可能,但在不了解实际问题域的情况下,所有名称都将是次优的。【参考方案8】:
您可以通过反射来完成此操作:
public Object getFirstNonNull(Object target, Method... methods)
Object value = null;
for (Method m : methods)
if ( (value = m.invoke(target)) != null)
break;
return value;
【讨论】:
反思应该是最后的手段。此代码不是类型安全的。以上是关于返回第一个非空值的主要内容,如果未能解决你的问题,请参考以下文章
返回 MIN 和 MAX 值并忽略空值 - 使用前面的非空值填充空值