我有很多功能:
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)
答案 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
处理每个步骤的原因。 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);
请注意,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;
}