使用和避免null
“
null
,糟糕透啦!” —— Doug Lea.
“我称null
为百亿美金的错误!” —— C. A. R. Hoare.
轻率地使用null
可能导致很多令人惊愕的问题。通过研究谷歌的代码,我们发现:95% 的集合不接受null
作为元素,因此相比于默默地接受null
,使用快速失败的操作拒绝null
值对开发者更有帮助。
此外,null
的模糊性会让人很不爽。我们很难知道返回值是null
代表着什么意思,例如当Map.get(key)
返回null
时,既可能是 Map 中对应key
的值是null
,也可能是 Map 中根本就没有对应key
的值。null
可以表示成功,也可以表示失败,几乎意味着任何事情。使用除null
之外的某些其他值,可以让你表达的含义更清晰。
在某些场景下,使用null
也确实是正确的。例如,在内存和速度方面,null
就是廉价的,而且在对象数组中,出现null
也是不可避免的。但是相对于库来说,在应用代码中,null
往往是导致混乱、疑难问题和含义模糊的根源。就像我们上面谈到的,当Map.get(key)
返回null
时,既可能是 Map 中对应key
的值是null
,也可能是 Map 中根本就没有对应key
的值。最关键的是,null
根本就没有给出空值到底意味着什么。
正是由于这些原因,很多的 Guava 工具类都被设计为针对null
是快速失败的,除非工具类本身对null
是友好的。此外,Guava 提供了很多工具类,可以让我们在必须使用null
时用起来更简单,也可以让我们避免使用null
.
具体案例
不要在Set
中使用null
,也不要把null
作为 Map 的键;在查询操作中,使用一个特殊值表示null
,这会让我们的语言更加清晰。
如果你想使用null
作为 Map 中某个键的值,最好不要这么做;单独的维护一个键为空或者非空的Set
更好一些。毕竟 Map 中对应于某个键的值为空,或者根本就没有值,这是很容易混淆的情况。因此,最好的方法就是将这些键分开,并且仔细想想,在你的应用中,值为null
的键到底有什么含义。
如果你在List
中使用null
,并且列表是稀疏的,那么使用Map<Integer, E>
可能会更高效,并且可能更符合你潜在的需求。
此外,我们可以考虑一下使用自然的null
对象的情况。虽然这样的情况并不多,但还是有的,例如有一个枚举类型,添加了一个常量来表示null
。还例如,在java.math.RoundingMode
里面有一个常量UNNECESSARY
,它表示一种不做任何舍入操作的模式,如果用这种模式做舍入操作,则会抛出异常。
如果你确实需要使用null
值,并且使用 Guava 的集合会有一些问题,那么你可以选择其他的实现。例如,使用 JDK 中的Collections.unmodifiableList
代替 Guava 中的ImmutableList
.
Optional
一般情况下,我们使用null
表示某种缺失的情况:或许在某个值应该存在的地方,没有值,或者根本就找不到对应的值。例如,通过 Map 的键来获取值的时候,如果对应于某个键的值不存在,Map.get
就会返回null
.
Optional<T>
是一个用非空的值代替引用T
有可能为空的方法。一个Optional
可能包括非空的T
引用(在这种情况下,我们称之为“引用存在”),也可能什么都不包含(在这种情况下,我们称之为“引用缺失”)。但无论如何,Optional
绝不会说它包含null
.
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
Optional
不打算直接模拟其他编程环境中的option
or maybe
语义,尽管它们确实有些相似。
在这里,我们列出了一些最常见的Optional
操作。
创建Optional
实例
这里给出的都是Optional
的静态方法。
方法 | 描述 |
---|---|
Optional.of(T) | 创建值非空的Optional 实例,如果值为空则快速失败 |
Optional.absent() | 返回某些类型引用缺失的Optional 实例 |
Optional.fromNullable(T) | 将可能为空的引用传入Option 实例,如果引用非空则表示存在;引用为null ,则表示缺失 |
查询方法
下面都是非静态的方法,因此需要特定的Optional<T>
实例来调用。
方法 | 描述 |
---|---|
boolean isPresent() | 如果Optional 包含非null 的引用,则返回true |
T get() | 返回Optional 所包含的实例,若引用缺失,则抛出java.lang.IllegalStateException |
T or(T) | 返回Optional 所包含的引用,若引用缺失,返回指定的值 |
T orNull() | 返回Optional 所包含的引用,若引用缺失,返回null . 此为fromNullable 的逆操作。 |
Set<T> asSet() | 返回Optional 所包含引用的单例不变集合,如果引用存在,返回一个只有单一元素的集合;如果引用缺失,返回一个空集合。 |
除了上面给出的方法之外,Optional
还提供了很多有用的工具方法,具体可以通过Javadoc
来查看详细的资料。
使用Optional
有什么意义?
除了增加null
的可读性之外,Optional
最大的优点就在于它是一种傻瓜式的防御机制。如果你想让你的程序编译通过,那么它就会强迫你去积极思考引用缺失的情况。使用null
很容易让我们忽略某些情况,尽管FindBugs
可以给我们提供帮助,但我们并不认为这种解决方法很好。
特别地,当你返回一个值的时候,既可以是引用存在也可以是引用缺失。你(或者其他人)更容易忘记other.method(a, b)
可以返回一个空值,就像你在实现一个方法other.method
的时候,你也可能忘记参数a
可以是一个null
值一样。而将方法的返回类型指定为Optional
,也可以迫使调用者思考返回为引用缺失的情形。
便利的方法
当你想使用某些默认值代替一个null
值的时候,可以使用MoreObjects.firstNonNull(T, T)
方法。就像这个方法的名字提示的一样,如果输入的两个参数值都为null
,则会抛出NullPointerException
异常。如果你使用Optional
的话,这里有一个更好的替换方案,例如first.or(second)
。
在Strings
类中,也提供了很多可以处理String
值可能为空的方法。特别得,我们提供了恰当的名称:
方法签名 |
---|
emptyToNull(String) |
isNullOrEmpty(String) |
nullToEmpty(String) |
我们要强调的是,这些方法主要用来与一些不友好的 API 进行交互,例如null
字符串和空字符串等等。每当你写下混淆null
和空字符串的时候,Guava 团队的成员都泪流满面。正确的做法是将空字符串和null
字符串区别对待,但如果把两者同等对待,这就是要出 bug 的节奏啊!
翻译声明:本文翻译自 GitHub,Google Guava - UsingAndAvoidingNullExplained.
———— ☆☆☆ —— 返回 -> Guava 中文指南 <- 目录 —— ☆☆☆ ————