原文作者: Roman Elizarov

原文地址: Null is your friend, not a mistake

译者:秉心说

Kotlin Island from Wikimedia by Pavlikhin, CC BY-SA 4.0

我使用 Java 语言编程已经很久很久了,掌握了通过 Java 编写和维护大型软件(百万行代码)应该注意些什么,并亲眼目睹了全行业都在竭力避免空指针异常 NullPointerException(NPE),它困扰着大大小小的 Java 类库。在 2009 年其发明者 Tony Hoare 承认空引用是他造成的一个 “十亿美元的错误”之前,人人已经意识到它的危险性。

在 1996 年 Java 1.0 发布时,这个问题还不是如此明显。让我们看一个典型的 Java API 的例子:File.list() 方法。它被用来列举文件夹中的内容,如下所示:

for(String name : new File("directory").list()) {
System.out.println(name);
}

仅当文件夹存在时上面的代码才会正常运行,否则将抛出 NPE,因为 list() 返回了 null。但是谁会写这样的代码呢?不仅仅 list() 方法的文档中清楚的说明了文件夹不存在时将返回 null,而且现代的 IDE 也会对特定的代码给你提出警告。

但是,当使用 Java 编程时,开发者经常犯这类错误。到目前为止,有大量研究表明它们是如何发生的。结果表明,大多数情况下,我们的 API 函数不应该返回 null,其他开发者也并不希望返回 null。在一些特殊情况下,比如缺省值,Java 中的惯例是返回一些 “空对象”(空集合,未填充的对象等等),或者抛出异常,比返回 null 更糟糕。这就是为什么要设计 Files.newDirectoryStream, 高级版本的 File.list,任何情况都不会出现 null。

所以当 null 在一些特殊情况下作为函数返回值时,如性能优化,未初始化的引用字段等等,它常常会让你措手不及,没有做好准备去处理它。不仅仅是必须处理空值的情况很少,而且在 Java 中用来处理空值的代码是很啰嗦的:

String[] list = new File("directory").list();
if (list != null) {
for (String name : list) {
System.out.println(name);
}
}

毫无疑问,除非真的需要(你的客户在生产环境发现了 NPE),不然你真的不想写这样的代码。

对 null 的恐惧导致了一些极端情况。有一些 Java 编码风格完全禁止 null,将可恶的工作交给开发者。不知道你有没有见过这样的 Java 类库,所有的域对象都要实现一个特殊的 Null 接口,并且提供手动编码生成的 “空对象” 实例。如果没有见过说明你还是幸运的。但是我敢打赌你已经看到了只为了避免空值而污染 Java 代码的 Optional <T> 包装器(译者注:Java 8 新特性)。

有些集合框架的 API 出于谨慎禁止 null 元素,并且一些 Java 核心团队成员认为 Java 集合框架对 null 的支持是一个错误。这让人非常难过。

事实上,null 这个概念不是一个错误,但是 Java 的类型系统认为 null 是任何类型的成员。 让我们看看,在 Java 中 “abc” 是一个合法的 Stringnull 也是一个合法的 String。你可以在前者上使用 string 的所有方法,例如 substring。但是在后者上使用则会发生运行时错误。它是类型安全的吗?并不完全是。一个类型的特定值在进行某些操作时发生运行时异常(例如除 0)是正常的,但是当对一个值进行该类型的所有操作都发生了异常,这首先表明的是这个值并不属于这个类型。所有的那些 NPE 都表明了 Java 的类型系统存在明显的缺陷。

更多的类型安全的编程语言,例如 Kotlin,通过合理的将 null 的概念合并到类型系统中来修复这个缺陷。添加检查和警告也有一定作用,但这并不够。显然,一个健全的类型系统必须允许 String 类型的所有变量都支持它的操作。所以在 Kotlin 中,将 null 赋给 String 类型的变量就不仅仅只是一个警告了,而是类型错误,就像把数值 42 赋给 String 类型变量一样。

类型系统中合理的 null 支持是 API 设计的一个转折点,没有任何理由再去害怕 null 了。一些函数返回可空类型 String?,另一些函数返回不可空类型 String,就和一些函数返回 String,另一些返回 Int 一样。它们都是不同的类型,有着不同但是安全的操作集。

用类型安全的 null 来表示 “缺失的值” 是更好,更高效,更简洁的。看一下 Kotlin 标准库中的 String.toIntOrNull() 函数,用于将 string 转为数字,不能转换的话返回 null。使用起来很愉快,编写一个命令行程序,接受 integer 参数并处理参数的缺失就很简单:

fun main(args: Array<String>) {
val id = args.getOrNull(0)?.toIntOrNull()
?: error("id expected")
// ...
}

在 API 设计中使用 null 吧,它是你和 Kotlin 的好朋友。没有理由去害怕它,也没有理由使用 null object 模式或者包装器来处理它,更不用说异常了。在你的 API 中合理使用 null 会给你带来更可读,更安全的代码,并且远离样板代码。

深入阅读

如果你喜欢这个主题,并且想了解更多关于语言设计的细节,那么可以考虑阅读这篇文章—— Dealing with absence of value

文章首发微信公众号: 秉心说 , 专注 Java 、 Android 原创知识分享,LeetCode 题解。

更多最新原创文章,扫码关注我吧!

Null is your firend, not a mistake的更多相关文章

  1. 为什么要使用Optional

    为什么使用Java Optional Why use Optional? NullPointerException 有个很有名的说法: Null Pointer References: The Bil ...

  2. 《深入理解JAVA虚拟机》笔记1

    java程序运行时的内存空间,按照虚拟机规范有下面几项: )程序计数器 指示下条命令执行地址.当然是线程私有,不然线程怎么能并行的起来. 不重要,占内存很小,忽略不计. )方法区 这个名字很让我迷惑. ...

  3. Non-Nullable Types vs C#: Fixing the Billion Dollar Mistake (转载)

    One of the top suggestions (currently #15 on uservoice) for improving C# is the addition of non-null ...

  4. 【小计】新人Tostring前忘记Null判断的处理

    ToString和string.Concat(可屏蔽Null的异常)性能相差不大,一些中小项目完全可以用Concat(新人容易忘记判断Null的情况,遇到太多了,所以建议重写tostring方法,内部 ...

  5. SQL Server-聚焦NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL性能分析(十八)

    前言 本节我们来综合比较NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL的性能,简短的内容,深入的理解,Always to review the basics. ...

  6. 异步 HttpContext.Current 为空null 另一种解决方法

    1.场景 在导入通讯录过程中,把导入的失败.成功的号码数进行统计,然后保存到session中,客户端通过轮询显示状态. 在实现过程中,使用的async调用方法,出现HttpContext.Curren ...

  7. js中的null 和undefined

    参考链接:http://blog.csdn.net/qq_26676207/article/details/53100912 http://www.ruanyifeng.com/blog/2014/0 ...

  8. JavaScript中undefined与null的区别

    通常情况下, 当我们试图访问某个不存在的或者没有赋值的变量时,就会得到一个undefined值.Javascript会自动将声明是没有进行初始化的变量设为undifined. 如果一个变量根本不存在会 ...

  9. SQLSERVER中NULL位图的作用

    SQLSERVER中NULL位图的作用 首先感谢宋沄剑提供的文章和sqlskill网站:www.sqlskills.com,看下面文章之前请先看一下下面两篇文章 SQL Server误区30日谈-Da ...

随机推荐

  1. c#Winform自定义控件-目录

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...

  2. Kaggle比赛(一)Titanic: Machine Learning from Disaster

    泰坦尼克号幸存预测是本小白接触的第一个Kaggle入门比赛,主要参考了以下两篇教程: https://www.cnblogs.com/star-zhao/p/9801196.html https:// ...

  3. net core Webapi 总目录

    目录 博客目录 代码地址 博客目录 net core Webapi基础工程搭建(一)--开发工具及环境 net core Webapi基础工程搭建(二)--创建工程 net core Webapi基础 ...

  4. 纯数据结构Java实现(6/11)(二叉堆&优先队列)

    堆其实也是树结构(或者说基于树结构),一般可以用堆实现优先队列. 二叉堆 堆可以用于实现其他高层数据结构,比如优先队列 而要实现一个堆,可以借助二叉树,其实现称为: 二叉堆 (使用二叉树表示的堆). ...

  5. from 表单用 GET 方法进行 URL 传值时后台无法获取问题

    问题描述 <a href="${pageContext.request.contextPath}/client?method=add">点我</a> < ...

  6. centos7搭建hadoop3.*.*系列

    最近搭建这个hadoop踩过不少坑,先是配置JDK搞错路径(普通用户和root用户下的路径不同),再就是hadoop版本不同导致的启动错误,网上找到的是hadoop2.*.*的版本,但是我安装的had ...

  7. javascript JS CryptoJS DES加解密CBC模式与C#DES加解密相同互通

    我们只知道不同的语言解密要相互通用,就需要遵循相同的加密方式,然而在具体做技术预研的时候,就发现会遇到很多问题,网上找的资料也是比较片面,所以我踩了坑,并且把解决方案和相关资料源码提供出来,给需要的朋 ...

  8. Features Track 2018徐州icpc网络赛 思维

    Morgana is learning computer vision, and he likes cats, too. One day he wants to find the cat moveme ...

  9. CodeForces 86 D Powerful array 莫队

    Powerful array 题意:求区间[l, r] 内的数的出现次数的平方 * 该数字. 题解:莫队离线操作, 然后加减位置的时候直接修改答案就好了. 这个题目中发现了一个很神奇的事情,本来数组开 ...

  10. 楼房重建 HYSBZ - 2957

    楼房重建 HYSBZ - 2957 第一次写分块, 写了之后觉得真的是暴力的一比. 题解:先讲n分成 sqrt(n)块,记得补上末尾的, 然后就是对于每一次更新操作, 都重新的讲这个块里面的有效楼放入 ...