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 ...
随机推荐
- 一份程序猿单词列表(updating)
以下单词是个人平时阅读英文文档时遇到的一些“生”单词,该文档将持续更新,可以持续关注https://github.com/hylinux1024/word-list-for-programmer hi ...
- 「雕爷学编程」Arduino动手做(15)——手指侦测心跳模块
37款传感器和模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器与模块,依照实践出真知(动手试试)的理念,以学习和交流为目的,这里准备 ...
- 对已经存在的hbase表修改压缩方式
业务上可能会遇到这种情况,在最初创建hbase表时候,未指定压缩方式,当数据导入之后,由rowkey带来的数据膨胀导致hdfs上的数据大小远远大于原始数据大小.所以这时候可能就不得不考虑使用压缩,但是 ...
- Redis集群环境下的键值空间监听事件实现方案
一直想记录工作中遇到的问题和解决的方法,奈何没有找到一方乐土,最近经常反思,是否需要记录平时的点滴,后台还是决定下定决心记录一些,以便以后用到的时候找不着,实现这样的一个功能主要也是业务所需要的. 需 ...
- 【MySql】linux下,设置mysql表名忽略大小写
[障碍再现] 状况描述01: 在LINUX下调一个程序经常报出找不到表,但是我明明是建了表的, 测试的时候,遇到一些问题,从Windows平台访问虚拟机中的Web应用,经常报出找不到表 ...
- 记:使用vue全家桶 + vux组件库 打包成 dcloud 5+ app 开发过程中遇到的问题
vue-cli 版本:2.9.6 webpack 版本:3.6.0 1. vue-cli 安装好之后,不是自动打开默认浏览器 在 config文件夹 ---> dev选项中,有个 autoO ...
- Flink的JobManager启动(源码分析)
都知道Flink中的角色分为Jobmanager,TaskManger 在启动脚本里面已经找到了jobmanager的启动类org.apache.flink.runtime.entrypoint.St ...
- offsetX、clientX、screenX、pageX、layerX
pageX/Y 原点相对于文档窗口左上角. offsetX/Y 原点相对于触发事件元素的左上角,需要注意的是,offset是有负值的,如果你的元素有边框,那么offset会是负值. (黑色为鼠标点击点 ...
- mysql/mariadb 初体验
距离申请这个博客号已经过了九个月,思前想后还是把知识沉淀放这里吧,不过初心一样,依旧是 '谨以此文,见证成果'.有 兴趣的话也欢迎大家去我的csdn博客转一转.以下是正文: 1.mysql安装 win ...
- 利用ShardingSphere-JDBC实现分库分表--配置中心的实现
在之前的文章中我详细描述了如何利用ShardingSphere-JDBC进行分库分表,同时也实现了简单的精确分库算法接口,详情见下面的链接: 利用ShardingSphere-JDBC实现分库分表 但 ...