原始 "nulls" 和 Java 8

Primitive "nulls" and Java 8

我知道处理 null 基元的最佳做法是使用盒装包装器,例如 Integer 而不是 int,如此处所述

Null for primitive data types

但是,今天 Java 8 仍然如此吗? 引入了可选原语,例如 OptionalInt,其 OptionalInt.empty() 有效地表示 null 值?我的理解是 Optionals 应该只用于方法 return 类型,而不是属性本身的类型。 "nullable" 基元是否仍应存储为盒装属性?在方法 return 类型中,它们应该只是 Optional 吗?或者在 属性 本身中存储为 OptionalInt

假设你有一个方法

public void logRequest(Integer userID, String content){
    //log content locally or in db or a rest call
}

假设您从数据库中获取特定用户的 userID。可以有以下几种可能:

  • 数据库returns一个用户ID。
  • 未找到 ID。

所以现在如果没有找到 id,你将什么传递给 logRequest

  • 你通过了null吗?如果该方法没有适当的空检查怎么办。它应该将 null 推断为什么?
  • 你应该通过 0 还是 -1?如果 null 表示 0 那么它可能与 ID 为 0 的实际用户相矛盾。怎么区分null和0?

当然,上述问题可以通过适当的文档和固定规则来解决,但它会增加维护成本并且容易出错。正确编写的代码应该是它自己的文档。

现在假设该方法声明为:

public void logRequest(OptionalInt userID, String content)

该方法本身非常清楚地表明它需要一个可选的参数。所以只有两种情况需要处理:发送一个适当的参数(这意味着使用 0 不绕过)。如果可选项为空,则方法正确地知道如何处理。 (不像以前我们必须阅读它的文档来预测行为)。

因此,它自己的方法声明清楚地说明了行为,而不是文档为您做这件事并依赖于文档和胡乱猜测。可选项在这种情况下非常有用。

PS:我总是觉得将 null 传递给期望 Integer 的方法很困难。绝对感觉不对!天哪,在 Java 中,潜意识总是将整数视为原始值。它们不应该被取消。

在您链接的问题的答案中,解释了为什么原始值不能是 null。没有丝毫建议使用装箱值表示 null 是“最佳实践”,事实上,根本没有人说使用 null 是一个好的实践。

与其尝试为原始类型模拟 null 值,不如退后一步,想想您要表达的内容。可以使用多种选项表达该语义,使用 null 不一定是最佳选择之一。表达未初始化或“清除”值的一种简单方法是使用额外的 boolean fooPropertyInitialized 变量。它比使用 null 更清晰,而且它的开销不一定比原始值的装箱大。

这也适用于 API。不要使用 Optional 作为 属性。提供一个clearFooProperty()方法即可,如果要支持这样的操作,再加一个isFooPropertyInitialized()。如果调用者未能先检查 initialized 状态,让 getFooProperty() 抛出异常会更清晰。

请注意,也可以简单地使用特定上下文有效范围之外的值来表示特殊条件。这是常见的做法,例如 InputStream.read() 方法 returning -1 表示到达终点。

Optional 操作 的理想 return 类型,而结果没有存储空间,因此您不能拆分查询是否存在并且该值分为两次方法调用,现值的取值范围不限。

但是如前所述,在不知道您想表达哪种语义的情况下,我们无法提出建议,并且在没有“最佳实践”的情况下可能会有不止一种可能的解决方案……