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 ...
随机推荐
- python random 模块及验证码功能
random模块 import random random.random() random.randint(1,3) # 1-3的整数包括3 import random print(random.ra ...
- laya2d 与 cad 之间的坐标转换
坐标系基本概念 直角坐标系可分为左手坐标系与右手坐标系,cad 中用到的是右手坐标系, Laya2D 中用到的是左手坐标系, Laya3D 中使用右手坐标系. 那么如何判断二维直角坐标系是左手还是右手 ...
- Python爬虫视频教程
├─第1章_[第0周]网络爬虫之前奏 │ ├─第1节_"网络爬虫"课程内容导学 │ │ 第1部分_全课程内容导学.mp4 │ │ 第2部分_全课程内容导学(WS00单元)学习资料. ...
- 使用MTA HTML5统计API来分析数据
使用MTA HTML5统计API来分析数据 在开发个人博客的时候,用到了腾讯移动分析(MTA),相比其他数据统计平台来说我喜欢她的简洁高效,易上手,同时文档也比较全面,提供了数据接口供用户调用. 在看 ...
- Nacos(七):Nacos共享配置
前言 本文参考文章: SpringCloud Alibaba - Nacos Config 自定义共享配置 前景回顾: Nacos(六):多环境下如何"管理"及"隔离&q ...
- 我常用的一些linux命令
之前做过两年的运维,用过很多命令,深切体会到某些linux命令熟练掌握后对效率提升有多大.举个简单的例子,在做了研发后经常会有跑一些数据,对于结果数据的处理,我们的产品同学一般都习惯于用excel做统 ...
- CodeForces 1058E
题意略. 思路:本题有两个关键点: 一.满足题设的区间条件 1.区间内1的个数和为偶数 2.区间内含1个数最多的那一项,它所含1的个数不得超过区间内1的个数和的一半. 二.长度超过60的区间必然满足上 ...
- CodeForces 909E
题意略. 思路:一个拓扑排序的题目吧.肯定是要先处理后面那个任务,再处理前面那个任务,我的思路是尽力先把主处理器能操作的先操作完,然后再把副处理器能操作完的再操作完,这样循环,直到处理完全部. 定义t ...
- Java集合框架之Set接口浅析
Java集合框架之Set接口浅析 一.java.util.Set接口综述: 这里只对Set接口做一简单综述,其具体实现类的分析,朋友们可关注我后续的博文 1.1Set接口简介 java.util.se ...
- Leetcode之深度优先搜索&回溯专题-638. 大礼包(Shopping Offers)
Leetcode之深度优先搜索&回溯专题-638. 大礼包(Shopping Offers) 深度优先搜索的解题详细介绍,点击 在LeetCode商店中, 有许多在售的物品. 然而,也有一些大 ...