java 返回第一个非空值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/43280207/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Return first non-null value
提问by lorenzocastillo
I have a number of functions:
我有很多功能:
String first(){}
String second(){}
...
String default(){}
Each can return a null value, except the default. each function can take different parameters. For example, first could take no arguments, second could take in a String, third could take three arguments, etc.What I'd like to do is something like:
每个都可以返回一个空值,默认值除外。每个函数可以采用不同的参数。例如,第一个可以不接受任何参数,第二个可以接受一个字符串,第三个可以接受三个参数等。我想做的是:
ObjectUtils.firstNonNull(first(), second(), ..., default());
The problem is that because of the function call, this does eager evaluation. Where'd I'd like to exit early,say after the second function (because the function calls can be expensive, think API calls, etc). In other languages, you can do something similar to this:
问题在于,由于函数调用,这会进行急切求值。我想早点退出,比如在第二个函数之后(因为函数调用可能很昂贵,想想 API 调用等)。在其他语言中,您可以执行类似的操作:
return first() || second() || ... || default()
In Java, I know I can do something like:
在 Java 中,我知道我可以执行以下操作:
String value;
if (value = first()) == null || (value = second()) == null ...
return value;
That's not very readable IMO because of all the == null checks.ObjectUtils.firstNonNull()creates a collection first, and then iterates, which is okay as long as the function gets evaluated lazily.
由于所有 == null 检查,这不是非常易读的 IMO。ObjectUtils.firstNonNull()首先创建一个集合,然后迭代,只要函数被惰性求值就可以了。
Suggestions? (besides doing a bunch of ifs)
建议?(除了做一堆ifs)
回答by Roland
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.
它在第一个非空值处停止,否则设置从 返回的值defaultOne
。只要你保持顺序,你就是安全的。当然,这需要 Java 8 或更高版本。
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.
它在第一次出现非空值时停止的原因是由于Stream
每个步骤的处理方式。Themap
是一个中间操作,也是filter
。的findFirst
另一侧是一个短路端子的操作。所以它继续下一个元素,直到一个匹配过滤器。如果没有元素匹配,则返回一个空的可选项,因此orElseGet
调用 -supplier。
this::first
, etc. are just method references. If they are static replace it with YourClassName::first
, etc.
this::first
等只是方法引用。如果它们是静态的,请将其替换为YourClassName::first
等。
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.
请注意,Supplier
只有在您调用get
它时才会对其进行评估。这样你就可以得到你的懒惰评估行为。供应商 lambda 表达式中的方法参数必须是最终的或有效的最终的。
回答by slim
This can be done pretty cleanly with a stream of Suppliers
.
这可以通过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.
这样做的原因是,尽管出现了,整个执行过程是由 驱动的findFirst()
,它从 中拉出一个项目,从filter()
懒惰地从 中拉出项目map()
,它调用get()
处理每次拉动。findFirst()
当一个项目通过过滤器时,将停止从流中提取,因此后续供应商将不会get()
调用。
Although I personallyfind the declarative Stream style cleaner and more expressive, you don't haveto use Stream to work with Supplier
s if you don't like the style:
虽然我个人觉得声明流风格更清洁,更表现,你不具备使用流来工作,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();
}
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.
很明显,如果您用尽列表中的选项,Optional
您可以同样地返回 a String
,而不是返回一个,或者返回 null (yuk),一个默认字符串,或者抛出一个异常。
回答by Edwin Buck
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.
现在,如果您可以更改界面,则可能会使界面更加友好。一个可能的示例是将步骤设为“ValueProvider”对象。
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
.
还有各种其他方法,但它们需要重构代码以使其更加面向对象。请记住,仅仅因为 Java 是一种面向对象的编程语言,并不意味着它将始终以面向对象的方式使用。该first()
... ...last()
方法列表很不-面向对象,因为它不建模List
。尽管方法名称具有表现力,但 a 上List
有一些方法,可以轻松地与for
循环和Iterators
.
回答by Nestor Sokil
If you are using java 8 you can convert these function calls to lambdas.
如果您使用的是 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();
}
If you don't want the generic implementation and use it only for String
s go on and just replace T
with String
:
如果您不想要通用实现并且仅将其用于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();
}
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 :)
PS btwdefault
是保留关键字,因此您不能将其用作方法名称 :)
EDIT: ok, the best way to do this would be to return Optional, then you don't need to pass default supplier separetely:
编辑:好的,最好的方法是返回 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();
}
回答by Ted Hopp
If you want to package it up into a utility method, you'll have to wrap each function up into something that defers execution. Perhaps something like this:
如果你想把它打包成一个实用方法,你必须把每个函数打包成一些延迟执行的东西。也许是这样的:
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();
}
You could use java.util.concurrent.Callable
instead of defining your own Wrapper
class, but then you'd have to deal with the exception that Callable.call()
is declared to throw.
您可以使用java.util.concurrent.Callable
而不是定义您自己的Wrapper
类,但是您必须处理Callable.call()
声明为抛出的异常。
This can then be called with:
然后可以使用以下命令调用它:
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(); },
...
);
In Java 8, as @dorukayhan points out, you can dispense with defining your own Wrapper
class and just use the Supplier
interface. Also, the call can be done much more cleanly with lambdas:
在 Java 8 中,正如@dorukayhan 指出的那样,您可以不用定义自己的Wrapper
类而只需使用Supplier
接口。此外,使用 lambda 可以更干净地完成调用:
String value = firstNonNull(
() -> defaultFunc(),
() -> first(),
() -> second(),
...
);
You can also (as @Oliver Charlesworth suggests) use method references as shorthand for the lambda expressions:
您还可以(如@Oliver Charlesworth 建议的那样)使用方法引用作为 lambda 表达式的简写:
String value = firstNonNull(
MyClass::defaultFunc,
MyClass::first,
MyClass::second,
...
);
I'm of two minds as to which is more readable.
对于哪个更具可读性,我有两种看法。
Alternatively, you can use one of the streaming solutions that many other answers have proposed.
或者,您可以使用许多其他答案提出的流媒体解决方案之一。
回答by DenisSt
Just make a class with one function like this:
只需使用一个函数创建一个类,如下所示:
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;
回答by Ryan S
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;
}