原文作者: 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. Java初学心得(一)

    Java中基本组成单元是类,在类中又包含属性和方法. 每个应用程序都包含一个main()方法,main方法里的称为主类. 一,基本变化 ①全局变量:在类中的属性 局部变量:在方法中的属性 ②基本数据类 ...

  2. 基于SpringBoot从零构建博客网站 - 分页显示文章列表功能

    显示文章列表一般都是采用分页显示,比如每页10篇文章显示.这样就不用每次就将所有的文章查询出来,而且当文章数量特别多的时候,如果一次性查询出来很容易出现OOM异常. 后台的分页插件采用的是mybati ...

  3. Jenkins将ASP.NETCore部署到Azure

    首先需要获得Azure上App-service 的porfile. 登录portal 选到app,点击Get publish pofile 将得到一个 ****.PublishSettings,注意这 ...

  4. Hive 系列(七)—— Hive 常用 DML 操作

    一.加载文件数据到表 1.1 语法 LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (p ...

  5. csdn论坛页抓取

    抓取csdn论坛 实现功能 获取论坛分类所有链接,并拼接成推荐精华页的完成的链接 获取推荐精华页的帖子状态,赏分,帖子标题,作者,发布时间,回复量,查看量,最后发表时间 置顶内容不爬取,只打印置顶内容 ...

  6. C++通过宏定义判断操作系统及编译器

    INTRODUCTION: C++的编译环境千奇百怪,很多时候一些代码在某些编译环境下可用,一旦移到其他环境下,就会干脆Compile Error 对此,我们可以使用C++的宏定义来判断操作系统,从而 ...

  7. ASP.NET Core on K8S深入学习(7)Dashboard知多少

    本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 在第二篇<部署过程解析与Dashboard>中介绍了如何部署Das ...

  8. idea(java)实用开发插件

    Idea常用的插件: mybatisX,      ----------------  (Alt + enter) codeGlace, Lombok, sonarlint, translation, ...

  9. BeanUtils开发包的使用

    对内省技术有了一定的了解之后,我们就可以来学习一下BeanUtils开发包的使用了. 我们先假设一个情景,有一个JSP文件,如果要将该JSP文件中表单数据封装到Servlet文件应该怎么办?此时方法显 ...

  10. Springboot源码分析之TargetSource

    摘要: 其实我第一次看见这个东西的时候也是不解,代理目标源不就是一个class嘛还需要封装干嘛... 其实proxy代理的不是target,而是TargetSource,这点非常重要,一定要分清楚!! ...