Null is your firend, not a mistake
原文作者: 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” 是一个合法的 String,null 也是一个合法的 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的更多相关文章
- 为什么要使用Optional
为什么使用Java Optional Why use Optional? NullPointerException 有个很有名的说法: Null Pointer References: The Bil ...
- 《深入理解JAVA虚拟机》笔记1
java程序运行时的内存空间,按照虚拟机规范有下面几项: )程序计数器 指示下条命令执行地址.当然是线程私有,不然线程怎么能并行的起来. 不重要,占内存很小,忽略不计. )方法区 这个名字很让我迷惑. ...
- 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 ...
- 【小计】新人Tostring前忘记Null判断的处理
ToString和string.Concat(可屏蔽Null的异常)性能相差不大,一些中小项目完全可以用Concat(新人容易忘记判断Null的情况,遇到太多了,所以建议重写tostring方法,内部 ...
- 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. ...
- 异步 HttpContext.Current 为空null 另一种解决方法
1.场景 在导入通讯录过程中,把导入的失败.成功的号码数进行统计,然后保存到session中,客户端通过轮询显示状态. 在实现过程中,使用的async调用方法,出现HttpContext.Curren ...
- js中的null 和undefined
参考链接:http://blog.csdn.net/qq_26676207/article/details/53100912 http://www.ruanyifeng.com/blog/2014/0 ...
- JavaScript中undefined与null的区别
通常情况下, 当我们试图访问某个不存在的或者没有赋值的变量时,就会得到一个undefined值.Javascript会自动将声明是没有进行初始化的变量设为undifined. 如果一个变量根本不存在会 ...
- SQLSERVER中NULL位图的作用
SQLSERVER中NULL位图的作用 首先感谢宋沄剑提供的文章和sqlskill网站:www.sqlskills.com,看下面文章之前请先看一下下面两篇文章 SQL Server误区30日谈-Da ...
随机推荐
- cs231n---生成模型
1 生成模型的定义和分类 生成模型是一种无监督学习方法.其定义是给一堆由真实分布产生的训练数据,我们的模型从中学习,然后以近似于真实的分布来产生新样本. 生成模型分为显式和隐式的生成模型: 为什么生成 ...
- Kafka 0.8 Producer (0.9以前版本适用)
Kafka旧版本producer由scala编写,0.9以后已经废除,但是很多公司还在使用0.9以前的版本,所以总结如下: 要注意包Producer是 kafka.javaapi.producer.P ...
- DRF (Django REST framework) 中的路由Routers
路由Routers 注意是:对于视图集ViewSet!!!我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息. REST frame ...
- Windos 上逆天又好用的软件有哪些?
谷歌浏览器 Chrome 浏览器是大名鼎鼎的科技公司谷歌开发的一款浏览器,国内的360浏览器等大多都是基于谷歌开源出的浏览器内核,然后给他穿了一层360的衣服.至于性能和启动速度上来讲,我个人觉得Ch ...
- Springboot源码分析之@Transactional
摘要: 对SpringBoot有多了解,其实就是看你对Spring Framework有多熟悉~ 比如SpringBoot大量的模块装配的设计模式,其实它属于Spring Framework提供的能力 ...
- vs2019+cmake实现Linux远程开发
在上一篇文章中我们介绍了使用vs2019作为远程Linux系统的开发环境,但我们是创建的传统的sln项目,而对于Linux开发者来说以autotools或是cmake进行项目结构的组织更为简单直观,也 ...
- Linux 设置服务开机启动
首先来了解一下 service命令是Redhat Linux兼容的发行版中用来控制系统服务的实用工具,它以启动.停止.重新启动和关闭系统服务,还可以显示所有系统服务的当前状态. service +(自 ...
- vue实现输入框的模糊查询(节流函数的应用场景)
上一篇讲到了javascript的节流函数和防抖函数,那么我们在实际场合中该如何运用呢? 首先,我们来理解一下:节流函数首先是节流,就是节约流量.内存的损耗,旨在提升性能,在高频率频发的事件中才会用到 ...
- Leetcode之深度优先搜索(DFS)专题-547. 朋友圈(Friend Circles)
Leetcode之深度优先搜索(DFS)专题-547. 朋友圈(Friend Circles) 深度优先搜索的解题详细介绍,点击 班上有 N 名学生.其中有些人是朋友,有些则不是.他们的友谊具有是传递 ...
- 从强转 byte 说起
折腾的心,颤抖的手,只因在 main 函数中执行了一次 int 强转 byte 的操作,输出结果太出所料,于是入坑,钻研良久,遂有此篇. 我们都知道,Java中有8中基本数据类型,每种类型都有取值范围 ...