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

201

是否有Java等效的SQL COALESCE函数?也就是说,有没有一种方法可以返回几个变量中第一个非空值?

例如:

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

我想要一个语句,能够返回 abc 的第一个非空值——在这个例子中,它将返回 b 或者 4.4。(类似于 SQL 方法 —— 返回 COALESCE(a,b,c))。我知道可以使用如下的显式语句实现:

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

但我想知道是否有任何内置的、被接受的函数来实现这一点。


3
通常情况下,您不需要这样的函数,因为如果'b'已经得出您想要的答案,您通常不会计算'c'。也就是说,您不会构建可能答案的列表,只保留一个答案。 - Peter Lawrey
注意:并非所有的关系型数据库在 COALESCE 函数中都支持短路计算。Oracle 最近才开始支持。 - Adam Gent
13个回答

294

3
我在寻找与之类似的字符串函数时,发现了SpringUtils.firstNonBlank(T...)和SpringUtils.firstNonBlank(T...)方法。请注意,我的翻译尽可能保持原意,同时使语言更通俗易懂,但不会添加任何解释性的内容。 - kodlan
2
请注意,这里列出的两种方法都会由于额外的对象分配而引入性能问题,这与简单的三元运算符或Guava的MoreObjects.firstNonNull不同。 - Vadzim
1
Stream.of(null,"a") will not work since the of function is annotated with @NonNull - pyropunk51
@pyropunk51 只有 Stream.of(null) 会抛出空指针异常。从 Java 11 开始,只要有一个以上的参数,任何参数都可以是 null - Volker Seibt

132
没有,没有这样的东西。
你能找到的最接近的是:
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 ...
}

效率的原因是每次调用方法的 ... 版本时都会发生数组分配。对于少量项目来说,这可能是浪费的,而我怀疑这将是常见的用法。

4
我之前提到的效率原因是,每次调用可变参数版本的方法时都会发生数组分配。对于少量项目而言,这可能是浪费的,我怀疑这种情况会经常出现。 - les2
8
我仍然会将其提取为一个私有帮助方法,而不是在代码中留下一个“看起来吓人”的条件块 - “那是做什么的?”这样,如果您需要再次使用它,可以使用IDE中的重构工具将方法移动到实用程序类中。具有命名方法有助于记录代码的意图,我认为这总是一件好事。 (非var-args版本的开销可能几乎无法测量。) - les2
13
注意:在 coalesce(a, b) 中,如果 b 是一个复杂表达式且 a 不为 null,仍会对 b 进行求值。但是,对于条件运算符 ?: 来说,情况并非如此。请参见 这个答案 - Pang
1
为了性能原因,这要求在调用coalesce之前预先计算每个参数是毫无意义的。 - Ivan G.
不要在Java中使用表达式调用此函数,因为Java目前还没有惰性求值。 - les2
显示剩余2条评论

69
如果只有两个测试参考,并且您使用的是Java 8,则可以使用:
Object o = null;
Object p = "p";
Object r = Optional.ofNullable( o ).orElse( p );
System.out.println( r );   // p

如果您导入静态 Optional,表达式就不会太糟糕。

不幸的是,使用 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

可以不使用可选项来实现: Object default = "some value"; Object res = ((res = getSomeNullable()) != null) ? res : default; - Stephan Richter

67

58
Objects.firstNonNull只有两个参数,Guava中没有相应的可变参数版本。同时,如果这两个参数都为null,则会抛出NullPointerException--这可能是期望的结果,也可能不是。 - user1114009
2
很好的评论,杰克。这个NullPointerException经常限制Objects.firstNonNull的使用。然而,这是Guava避免空值的方法。 - Anton Shchastnyi
4
这种方法现在已经被弃用,推荐使用MoreObjects.firstNonNull - davidwebster48
1
如果不希望出现NPE,则请参见此答案 - OrangeDog

26

继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
对于美观方面我给一个赞。虽然不确定与简单循环相比是否有更高的效率优势,但如果你想通过这种方式提高一点效率,那么它也应该是美观的。 - Carl Manaster
3
这种方式使得编写重载变体更加轻松,并减少了错误的可能性! - les2
3
高效版本的重点是使用varargs来避免浪费内存分配数组。在这里,通过为每个嵌套的coalesce()调用创建堆栈帧,您正在浪费内存。调用coalesce(a, b, c, d, e)最多会创建3个堆栈帧进行计算。 - Luke
重载地狱 - Lluis Martinez

12

这种情况需要用到一些预处理器。因为如果您编写一个函数(静态方法)来选择第一个非 null 值,则会评估所有项目。如果某些项目是方法调用(可能是时间昂贵的方法调用),这将是一个问题。并且即使在它们之前有任何项不为 null,这些方法也会被调用。

一些类似以下的函数:

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

在编译成字节码之前应该使用它,但需要一个预处理器来查找这个“合并函数”的用法,并将其替换为类似的结构

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

更新于2014-09-02:

由于Java 8和Lambdas的出现,现在Java也有了真正的coalesce!包括关键功能:只有在需要时才评估特定表达式——如果早期的表达式不为null,则不会评估后续的表达式(不会调用方法,不会进行计算或磁盘/网络操作)。

我写了一篇关于它的文章Java 8: coalesce – hledáme neNULLové hodnoty(用捷克语编写,但我希望代码示例对每个人都是可理解的)。


1
不错的文章 - 不过如果能有英文版就更好了。 - quantum
1
那个博客页面似乎无法与谷歌翻译配合使用。 :-( - HairOfTheDog

8
你可以尝试这样做:
public static <T> T coalesce(T... t) {
    return Stream.of(t).filter(Objects::nonNull).findFirst().orElse(null);
}

基于这个回答。

8
自Java 9以来,已有内置的Objects.requireNonNullElse方法用于两个参数的合并。对我来说,这是最有用的。

需要 Android 30。 - Mattwmaster58

5
当您想避免评估某些昂贵的方法时,使用供应商如何?像这样:
```html

当您想避免评估某些昂贵的方法时,使用供应商如何?像这样:

```
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
1
这可能有点老了,但为了完整起见:@Triqui的方法并不一定评估传递给coalesce的所有参数 - 这就是参数类型为Supplier的要点。只有在调用get()时才会对它们进行评估 - 如果第一个参数符合条件,则无需评估其余参数。 - filpa

5

使用Guava,您可以进行以下操作:

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

如果ab都是null,不会抛出NPE异常。

编辑:我错了,它确实会抛出NPE异常。正如Michal Čizmazia所评论的那样,正确的方式是:

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

1
嘿,它的意思是:java.lang.NullPointerException: 使用Optional.orNull()而不是Optional.or(null) - Michal Čizmazia
1
这就是诀窍:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull() - Michal Čizmazia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接