Java基础、中级、高级、架构面试资料

1 行代码,三个 null,Java 拒绝了 Elvis 操作符

JAVA herman 76浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云

NPE 让我们深夜加班,JEP 358 救我们的命,但“?.”操作符却被 Oracle,也就是 Java “拒之门外”。

每个 Java 程序员都曾被#NullPointerException(NPE)支配过,尤其是初入行的时候。1 行看似简单的user.getGroup().getLeader().getDeptName()代码,一旦抛出 NPE,不少人曾经对着堆栈信息抓耳挠腮,猜测到底是 user、group 还是 leader 为 null?

好在从 Java 14 引入的 JEP 358 特性之后,终结了这场“猜谜游戏”,但是这并没有解决 null 带来的 NPE 问题。Java 反而引入了一个颇具争议的 Optional,试图让 NPE 问题得到更稳妥的处理,但是这个 Optional 在社区里的“名声”非常不好,被多数人称为鸡肋。具体可以参考我的这些文章《https://mp.weixin.qq.com/s/gdvGqd0NSae9Y1vne99uVQ》、《https://mp.weixin.qq.com/s/Ard-MCSFITVcvyL71XQchQ》。

面对这种,1 行代码,三个 null,Kotlin 有 “?.”,Java 为什么没有?.,这个安全导航操作符至今仍未加入 Java 大家庭的原因是什么?今天我们就一起来聊一聊。

JEP 358

Java 14 发布的时候,我看到了 JEP 358,我本以为看到了希望,这些年才知道,我看到的是失望🤣。

这个提案要解决的问题是,详细精确的指出是具体是哪个变量或表达式为 null。

在这个提案之前,比如类似下面这个异常。

Exception in thread "main" java.lang.NullPointerException
    at com.xttblog.NpeTest.main(NpeTest.java:5)

我们对这个空异常破案时,就像侦探破案只给了你一个案发地点(第 5 行),却没有任何关于凶手(哪个变量为 null)的线索。对于a.b.c.i = 99;这样的链式调用,根本无法从堆栈中判断是 a、b 还是 c 为 null。通常,我们只能通过添加日志、断点调试,甚至重构代码来拆分调用链,才能定位问题。这对于线上故障排查来说,效率极低。

于是,Java 为了解决这个长期痛点,OpenJDK 在 Java 14 中正式引入了#JEP 358: Helpful NullPointerExceptions #业余草(友好的空指针异常)。

这项特性让 JVM 在抛出 NPE 时,不再只是冷冰冰地告诉你“出事了”,而是会提供一份详细的“目击者证词”,精确指出是哪个变量或表达式为 null。

// 代码: a.b.c.i = 99;
Exception in thread "main" java.lang.NullPointerException:
Cannot read field "c" because "a.b" is null
    at NpeTest.main(NpeTest.java:5)

Kotlin 的 “?.”

用过 Kotlin,或见过 Kotlin 的应该知道,它有一个?.式的语法。支持下面这样的链式调用。

// 即使 return null 返回空,也不会抛出 NPE

String text = user.getGroup()?.getLeader()?.getDeptName();

val deptName = user?.group?.leader?.deptName

这就是 Elvis 表达式 (Null Coalescing)给它的一个兜底。

类似?.这样的安全导航操作符(Safe Navigation Operator)在 Groovy、Kotlin、JavaScript、C#、Swift、Ruby 中都得到了类似的支持,然而 Java 却很头铁,它不支持。

事实上,在 Java 7 的早期讨论中,就曾有人提议加入类似“Elvis 操作符”的语法糖,但最终被拒绝了。具体的细节参考这篇文章https://mail.openjdk.org/pipermail/coin-dev/2009-March/000047.html

Java 拒绝的原因

社区里总结了不少 Java 拒绝的原因,但我认为这很可能都是表面原因。

更深层次的原因是,java 7 那个时候,面临着被收购,以及众多特性难产的原因,没有余力来实现一个极其复杂的操作符。

要知道,那个时候,Java 7 延期、延期,还是延期,包括 lambda 在内的众多特性都砍掉,放到 Java 8 了。

所以,那个时候,Java 团队给出的原因,我一点也不觉得是阻碍。

  • 语言哲学:Java 的设计哲学倾向于显式和明确。核心开发团队认为,使用 Optional 类或传统的 if 判断虽然代码略显冗长,但逻辑更清晰,意图更明确。
  • 已有替代方案:Java 8 引入的 Optional 类就是官方推荐的处理可能为 null 的值的方式。虽然它在处理深层嵌套时依然不够优雅,但它提供了一种函数式的、链式调用的解决方案。
  • 兼容性与语法复杂性:向一门成熟且庞大的语言中添加新的操作符,需要考虑其与现有语法、泛型、类型推断等特性的交互,评估其带来的复杂性是否值得。
  • Java 的核心设计原则之一: “显式优于隐式”(Explicit is better than implicit)。

总之,这类?.安全导航操作符或可选链操作符,在 Java 7 时期就被拒绝了。那时,就有社区提议引入类似?.?:(Elvis 操作符)的语法糖,但都被当时的语言团队(由 Gilad Bracha 等人主导)明确拒绝,理由之一正是“破坏语言一致性”和“鼓励不良实践”。

Java 推荐方案

就这样,一些 Java 热爱者由粉转黑了。而官方推荐拥抱 Optional,原因之一就是我说的,它实现起来更简单。

String deptName = Optional.ofNullable(user)
    .map(User::getGroup)
    .map(Group::getLeader)
    .map(Leader::getDeptName)
    .orElse("Unknown");

更有“恶心”的人,封装了一个搞笑的工具。

public static <T> T safeGet(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

// 使用
String deptName = safeGet(() -> user.getGroup().getLeader().getDeptName(), "业余草");

当然,也有一些第三方框架或三方库,如 Vavr(以前叫 Javaslang),提供了更强大的 Option 类型和模式匹配,能写出更接近函数式语言的代码。

总结

我个人觉得类似?.这类安全导航操作符(Safe Navigation Operator),也有人称为 “空感知操作符” 或 “可选链操作符(Optional Chaining Operator)”,没有被正式推出,是有一部分历史原因的。

而现在的 Java 语言架构师(如 Brian Goetz)也多次在公开场合表示,他们更关注模式匹配、值类型、泛型增强等“结构性改进”,而非增加“语法糖”。

社区里,虽然也有一小部分呼声,但未形成足够推动力。相反,大家认为在云原生时代, Project Loom(虚拟线程)、Project Valhalla(值类型)等重大特性的优先级更高,?.的优先级被遗忘到了角落。更何况现在有了 Option,?.这类特性或许难有“出头之日”。

参考资料

  • https://mail.openjdk.org/pipermail/coin-dev/2009-March/000047.html
  • https://openjdk.org/jeps/358
  • https://en.wikipedia.org/wiki/Safe_navigation_operator
  • https://stackoverflow.com/questions/61042145/java-14-nullpointerexception-no-detailed-message
  • https://vavr.io
  • https://www.baeldung.com/java-8-elvis-operator-implementation

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » 1 行代码,三个 null,Java 拒绝了 Elvis 操作符