如何获取Java中的第一个非空值?


153

Java是否等效于SQL COALESCE函数?也就是说,有什么方法可以返回多个变量的第一个非空值?

例如

Double a = null;
Double b = 4.4;
Double c = null;

我想无论如何都将返回的第一个非NULL值的语句ab以及c-在这种情况下,它会返回b,或4.4。(类似于sql方法-return COALESCE(a,b,c))。我知道我可以使用类似的方法来明确地做到这一点:

return a != null ? a : (b != null ? b : c)

但是我想知道是否有内置的,可接受的功能来完成此任务。


3
您不需要这样的函数,因为如果“ b”具有所需的答案,通常不会计算“ c”。也就是说,您不会只保留一个答案的清单。
彼得·劳瑞

注意:并非所有RDBMS都会在COALESCE上短路。Oracle只是最近才开始这样做。
亚当·根特

3
@ BrainSlugs83认真吗?Java应该吗?
德米特里·金茨堡

Answers:


108

不,没有。

您可以获得的最接近的是:

public static <T> T coalesce(T ...items) {
    for(T i : items) if(i != null) return i;
    return null;
}

出于有效的原因,您可以按以下方式处理常见情况:

public static <T> T coalesce(T a, T b) {
    return a == null ? b : a;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : (b != null ? b : c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return ...
}

3
我上面提到的效率原因是每次调用该方法的var arg版本时都会发生数组分配。这对于满手的物品可能是浪费的,我怀疑这是常见用法。
les2

凉。谢谢。在那种情况下,在这种情况下,我可能会坚持使用嵌套的条件运算符,因为这是唯一必须使用的条件运算符,并且用户定义的方法可能会过大...
弗罗迪(Froadie)2010年

8
我仍然会将其拉入私有帮助器方法中,而不是在代码中留下“看上去很恐怖”的条件块-“这是做什么的?” 这样,如果您确实需要再次使用它,则可以使用IDE中的重构工具将方法移至实用程序类。使用命名方法有助于记录代码的意图,这始终是一件好事,IMO。(非var-args版本的开销可能几乎无法测量。)
les2 2010年

10
注意:在中coalesce(a, b),如果b是一个复杂的表达式而a不是nullb则仍然被评估。?:条件运算符不是这种情况。看到这个答案

这就要求在调用合并之前对每个参数进行预先计算,出于性能考虑,这毫无意义
Ivan G.


59

如果只有两个变量要检查并且您正在使用Guava,则可以使用MoreObjects.firstNonNull(T first,T second)


49
Objects.firstNonNull只接受两个参数。番石榴中没有等效的可变参数。另外,如果两个arg都为null,则抛出NullPointerException-这可能是不希望的。

2
好评论,杰克。此NullPointerException通常会限制Objects.firstNonNull的用法。但是,Guava完全避免使用null的方法。
Anton Shchastnyi 2014年

4
该方法现已弃用,建议的替代方法是MoreObjects.firstNonNull
davidwebster48

1
如果不需要NPE,请查看此答案
-OrangeDog

51

如果只有两个参考要测试,并且您使用的是Java 8,则可以使用

Object o = null;
Object p = "p";
Object r = Optional.ofNullable( o ).orElse( p );
System.out.println( r );   // p

如果您导入static Optional,则表达式不会太差。

不幸的是,使用“可选方法”无法解决“几个变量”的问题。相反,您可以使用:

Object o = null;
Object p = null;
Object q = "p";

Optional<Object> r = Stream.of( o, p, q ).filter( Objects::nonNull ).findFirst();
System.out.println( r.orElse(null) );   // p

23

遵循LES2的答案,您可以通过调用重载函数来消除高效版本中的某些重复:

public static <T> T coalesce(T a, T b) {
    return a != null ? a : b;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : coalesce(b,c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return a != null ? a : coalesce(b,c,d);
}
public static <T> T coalesce(T a, T b, T c, T d, T e) {
    return a != null ? a : coalesce(b,c,d,e);
}

5
+1代表漂亮。不确定通过简单循环带来的效率优势,但是如果您要以这种方式评估任何微小的效率,那么效果可能还不错。
卡尔·马纳斯特

3
这样可以减少编写重载变体的痛苦,并减少出错的可能性!
les2

2
有效版本的目的是不浪费内存使用分配数组varargs。在这里,您通过为每个嵌套coalesce()调用创建一个堆栈框架来浪费内存。调用coalesce(a, b, c, d, e)最多创建3个堆栈帧进行计算。
路加福音

10

这种情况需要一些预处理器。因为如果编写一个选择第一个非空值的函数(静态方法),它将评估所有项目。如果某些项目是方法调用(可能是耗时的方法调用),则会出现问题。即使此方法之前的任何项不为null,也将调用此方法。

像这样的一些功能

public static <T> T coalesce(T ...items) 

应该使用,但是在编译成字节码之前,应该有一个预处理器,该预处理器可以找到此“ coalesce函数”的用法,并将其替换为类似

a != null ? a : (b != null ? b : c)

更新2014-09-02:

多亏了Java 8和Lambdas,Java才有可能真正融合!包括关键特性:仅在需要时才对特定表达式进行求值-如果较早的表达式不为null,则不对随后的表达式求值(不调用方法,不执行计算或磁盘/网络操作)。

我写了一篇有关Java 8的文章:Coalesce(联合语言)(用捷克语编写,但我希望每个人都能理解代码示例)。


1
不错的文章-但是拥有英语会很好。
量子

1
该博客页面存在某些不适用于Google翻译的内容。:-(
HairOfTheDog

5

使用番石榴,您可以:

Optional.fromNullable(a).or(b);

如果ab都为N则不会抛出NPE null

编辑:我错了,它确实会引发NPE。MichalČizmazia评论的正确方法是:

Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull();

1
嘿,确实如此:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
MichalČizmazia2013年

1
这可以解决问题:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
MichalČizmazia2013年

4

仅出于完整性考虑,“几个变量”的情况确实是可能的,尽管一点也不优雅。例如,对于变量op以及q

Optional.ofNullable( o ).orElseGet(()-> Optional.ofNullable( p ).orElseGet(()-> q ) )

请注意,使用orElseGet()参加的情况下opq不变量,但无论是表达昂贵或有不良副作用。

在最一般的情况下 coalesce(e[1],e[2],e[3],...,e[N])

coalesce-expression(i) ==  e[i]  when i = N
coalesce-expression(i) ==  Optional.ofNullable( e[i] ).orElseGet(()-> coalesce-expression(i+1) )  when i < N

这会导致表达式过长。但是,如果我们试图进入一个没有世界的世界null,那么v[i]很可能已经是类型Optional<String>,而不是简单String。在这种情况下,

result= o.orElse(p.orElse(q.get())) ;

或在表达式的情况下:

result= o.orElseGet(()-> p.orElseGet(()-> q.get() ) ) ;

此外,如果你也正在为功能-声明样式,op,和q应类型的Supplier<String>像:

Supplier<String> q= ()-> q-expr ;
Supplier<String> p= ()-> Optional.ofNullable(p-expr).orElseGet( q ) ;
Supplier<String> o= ()-> Optional.ofNullable(o-expr).orElseGet( p ) ;

然后整体coalesce简化为o.get()

举一个更具体的例子:

Supplier<Integer> hardcodedDefaultAge= ()-> 99 ;
Supplier<Integer> defaultAge= ()-> defaultAgeFromDatabase().orElseGet( hardcodedDefaultAge ) ;
Supplier<Integer> ageInStore= ()-> ageFromDatabase(memberId).orElseGet( defaultAge ) ;
Supplier<Integer> effectiveAge= ()-> ageFromInput().orElseGet( ageInStore ) ;

defaultAgeFromDatabase()ageFromDatabase()并且自然ageFromInput()会返回Optional<Integer>

然后coalesce变成effectiveAge.get()或仅仅是effectiveAge如果我们对一个满意Supplier<Integer>

恕我直言,使用Java 8时,我们将看到越来越多的这样的代码结构,因为它同时具有极强的自我解释性和效率,尤其是在更复杂的情况下。

我确实错过了只Lazy<T>调用Supplier<T>一次但懒惰的类以及Optional<T>(例如Optional<T>- Optional<T>运算符,甚至Supplier<Optional<T>>)定义的一致性。


4

您可以尝试以下方法:

public static <T> T coalesce(T... t) {
    return Stream.of(t).filter(Objects::nonNull).findFirst().orElse(null);
}

根据回应


3

如果要避免评估某些昂贵的方法,如何使用供应商?

像这样:

public static <T> T coalesce(Supplier<T>... items) {
for (Supplier<T> item : items) {
    T value = item.get();
    if (value != null) {
        return value;
    }
    return null;
}

然后像这样使用它:

Double amount = coalesce(order::firstAmount, order::secondAmount, order::thirdAmount)

您也可以对带有两个,三个或四个参数的调用使用重载方法。

此外,您还可以将流与以下内容一起使用:

public static <T> T coalesce2(Supplier<T>... s) {
    return Arrays.stream(s).map(Supplier::get).filter(Objects::nonNull).findFirst().orElse(null);
}

Supplier如果仍然要检查第一个参数,为什么还要将其包装在中?为了统一?
Inego

0

怎么样:

firstNonNull = FluentIterable.from(
    Lists.newArrayList( a, b, c, ... ) )
        .firstMatch( Predicates.notNull() )
            .or( someKnownNonNullDefault );

Java ArrayList方便地允许空条目,并且此表达式是一致的,而不考虑要考虑的对象数量。(以这种形式,所有考虑的对象都必须是同一类型。)


-3
Object coalesce(Object... objects)
{
    for(Object o : object)
        if(o != null)
            return o;
    return null;
}

2
上帝,我讨厌仿制药。我立刻明白了你的意思。我不得不看两次@ LES2才能弄清楚他在做同样的事情(并且可能“更好”)!为清楚起见+1
Bill K 2010年

是的,仿制药是必经之路。但是我对复杂性不是很熟悉。
埃里克

10
是时候学习泛型了:-)。@ LES2的示例与此之间没有什么区别,除了T代替Object之外。-1用于构建将强制将返回值强制转换为Double的函数。还要用全大写字母命名Java方法,这在SQL中可能没问题,但在Java中不是好的样式。
阿维

1
我意识到大写是不正确的做法。我只是向OP展示了如何使用他们要求的名称编写函数。商定,撤回Double的目标远非理想。我只是不知道可以为静态函数提供类型参数。我以为那只是上课。
埃里克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.