Java 8中的Optional classs是帮助开发者管理数据的有用工具。但关于如何使用它的建议却各不相同。这是我对Java 8中使用Optional 的一个好方法的看法。
请注意,本文假设你知道什么是Optional ,以及它是如何工作的。更多信息请参见我之前的文章和其他教程。另外,请注意,Optional 是一个争论激烈的话题,一些评论家容易对其重要性感到过于兴奋。
在Java 8中使用Optional的实用方法
下面是我发现的在Java 8中使用Optional 的具体方法,非常有用。应该考虑的是,这个方法是在编写新的应用程序,而不是维护现有的应用程序的情况下制定的。 有五个基本点。
- 不要声明任何类型为
Optional的实例变量。 - 使用
null来表示类的私有范围内的可选数据。 - 在访问可选字段的getters中使用
Optional。 - 不要在设置器或构造器中使用
Optional。 - 对于任何其他有可选结果的业务逻辑方法,使用
Optional作为返回类型。
比如说。
public class Address {
private final String addressLine; // never null
private final String city; // never null
private final String postcode; // optional, thus may be null
// constructor ensures non-null fields really are non-null
// optional field can just be stored directly, as null means optional
public Address(String addressLine, String city, String postcode) {
this.addressLine = Preconditions.chckNotNull(addressLine);
this.city = Preconditions.chckNotNull(city);
this.postcode = postcode;
}
// normal getters
public String getAddressLine() { return addressLine; }
public String getCity() { return city; }
// special getter for optional field
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
// return optional instead of null for business logic methods that may not find a result
public static Optional<Address> findAddress(String userInput) {
return ... // find the address, returning Optional.empty() if not found
}
}
首先要注意的是,我们的地址API的这个用户被保护不接受null。调用getAddressLine() 或getCity() 将总是返回一个非空值,因为地址对象在这些字段中不能容纳null。调用getPostcode() 将返回一个Optional<String> 实例,迫使调用者至少要考虑到数据丢失的可能性。最后,findPostcode() 也返回一个Optional 。 这些方法都不能返回null。
在对象内部,开发者仍然被迫考虑null,并使用!= null 检查来管理它。这是合理的,因为null的问题是受限制的。代码将全部作为一个单元来编写和测试(你确实写了测试,不是吗?),所以null不会引起很多问题。
从本质上讲,这种方法所做的是专注于在API边界的返回类型中使用Optional ,而不是在一个类内或输入上。与将其作为一个字段使用相比,optional现在是即时创建的。 这里的关键区别在于Optional 实例的生命周期。
通常情况下,域对象会在内存中停留一段时间,因为应用程序中的处理过程会发生,这使得每个可选实例的寿命相当长(与域对象的寿命有关)。相比之下,从getter返回的Optional 实例可能是非常短暂的。 调用者会调用getter,解释结果,然后继续前进。 如果你对垃圾收集有所了解,你会知道JVM会很好地处理这些短命的对象。 此外,当Optional 实例的寿命很短时,有更多潜在的热点来移除它的费用。虽然很容易声称这是 "不成熟的优化",但作为工程师,我们有责任了解我们工作的系统的限制和能力,并谨慎选择应该强调的点。
虽然这是个小问题,但应该注意的是,该类可以是Serializable ,如果任何字段是Optional ,就不可能做到这一点(因为Optional 没有实现Serializable )。
上面的方法没有对输入使用Optional ,比如设置器或构造器。虽然接受Optional 也可以,但根据我的经验,在设置器或构造器上使用Optional对调用者来说是很烦人的,因为他们通常有实际的对象。 迫使调用者用Optional 包裹参数是我不愿意给用户带来的烦恼。(即方便胜过输入的严格性
在缺点上,这种方法导致对象不是豆子。 获取器的返回类型与字段的类型不匹配,这可能会给一些工具带来问题。 在采用这种方法之前,请检查你使用的任何工具是否能够处理它,例如直接访问字段。
如果在一个应用程序中广泛采用这种方法,null的问题往往会不费吹灰之力地消失。 因为每个域对象都拒绝返回null,所以应用程序往往不会有null的传递。 根据我的经验,采用这种方法往往会导致代码中的null永远不会在类的私有范围之外使用。 而且重要的是,这种情况自然发生,没有痛苦的过渡。 随着时间的推移,你开始减少写防御性代码,因为你更确信没有变量会真正包含null 。
要使这种方法在基础知识之外发挥作用,关键是要学习Optional 上的各种方法。如果你只是简单地调用Optional.get() ,你就错过了这个类的全部意义。
例如,这里有一些处理XML解析的代码,其中有 "effectiveDate "或 "relativeEffectiveDate "的存在。
AdjustableDate startDate = tradeEl.getChildOptional("effectiveDate")
.map(el -> parseKnownDate(el))
.orElseGet(() -> parseRelativeDate(tradeEl.getChildSingle("relativeEffectiveDate")));
将此分解,tradeEl.getChildOptional("effectiveDate") 返回一个Optional<XmlElement> 。如果找到了该元素,则调用map() 函数来解析该日期。如果没有找到该元素,则调用orElseGet() 函数来解析相对日期。
对于一个使用这种方法的大型企业式代码库Optional ,见OpenGamma Strata,一个用于金融业的现代开源工具包。
也请看Joda-Beans代码生成,它可以生成这种模式(以及更多)。
最后,应该指出的是,未来的某个Java版本,即Java 9之后,可能会支持值类型。在这个未来的世界里,与Optional 相关的成本将消失,更广泛地使用它将是有意义的。我只是认为,现在还不是采用 "到处可选 "方法的时候。
总结
本文概述了在Java 8中使用Optional 的实用方法。如果在一个新的应用程序中持续遵循,null的问题往往就会逐渐消失。
有什么意见吗?