Java 8 SE可选,严格的方法

大约两周前,Stephen Colebourne提出使用Optional的实用方法 如果您阅读了它,您可能会从我以前的建议中猜到不同意。

总览

我必须以免责声明开头,但随后我将直接解释为什么我认为他的方法不够理想。

所有不归因于他人的报价均摘自Stephen的帖子。 虽然并非绝对必要,但我建议先阅读它。 但是别忘了回来!

我创建了三个要点,这些要点在整个帖子中都会介绍: Stephen版本基本版本扩展版本中的相同示例。

免责声明

Stephen Colebourne是Java的**人物。 引用Markus Eisele的Java英雄关于他的文章:

Stephen Colebourne是OpenGamma的技术人员。 他以其在开源和博客中的工作而闻名。 他创建了Joda-Time,现在将其进一步开发为JSR-310 / ThreeTen。 他为关于Java未来的辩论做出了贡献,包括为泛型和FCM闭包的钻石运算符提出的建议,两者都接近于Java 7和8中已采用的更改。Stephen经常担任会议发言人,JavaOne Rock Star和Java Champion。 。

我很高兴为斯蒂芬的房地产联盟做出贡献,这增强了我对他作为一个非常有能力的开发商和一个非常有思想的人的看法。

所有这些都表明,如果有疑问,请相信他。

事实是,他的方法根植于公理,即Optional应该仅用作返回类型。 这完全符合那些首先介绍该课程的人的建议。 引用Brian Goetz的话

当然,人们会做他们想要的。 但是,添加此功能时我们确实有明确的意图,并且它并不是通用的Maybe或Some类型,这是许多人希望我们这样做的原因。 我们的意图是为库方法返回类型提供一种有限的机制,其中需要一种明确的方式来表示“无结果”,而为此使用null则极有可能导致错误。

[…]几乎永远不要将其用作某些内容或方法参数的字段。

因此,如果有疑问,请相信他对我的看法。

并置

当然,比只相信任何人更好的是下定决心。 因此,与斯蒂芬的观点相反,这是我的观点。

基本要点

这些是斯蒂芬的五个基本要点:

  1. 不要声明任何类型为Optional的实例变量。
  2. 使用null表示类的私有范围内的可选数据。
  3. 对于访问可选字段的吸气剂,请使用可选。
  4. 不要在setter或构造方法中使用Optional。
  5. 对于具有可选结果的任何其他业务逻辑方法,请使用Optional作为返回类型。

这是我的:

  1. 设计代码,以尽可能避免可选性。
  2. 在所有其他情况下,请选择Optional而不是null。

例子

让我们比较例子。 他的是:

Address.java,作者:Stephen Colebourne

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
	}

}

我喜欢此类的任何使用者都不能接收null。 我不喜欢您仍然需要如何处理-在课堂上还是在课堂上。

这将是我的(基本)版本:

我的Address.java(基本版)

public class Address {

	// look ma, no comments required

	private final String addressLine;
	private final String city;
	private final Optional<String> postcode;

	// nobody has to look at this constructor to check which parameters are
	// allowed to be null because of course none are!

	public Address(String addressLine, String city, Optional<String> postcode) {
		this.addressLine = requireNonNull(addressLine,
				"The argument 'addressLine' must not be null.");
		this.city = requireNonNull(city,
				"The argument 'city' must not be null.");
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	// of course methods that might not have a result
	// return 'Optional' instead of null

	public static Optional<Address> findAddress(String userInput) {
		// find the address, returning Optional.empty() if not found
	}

	// getters are straight forward and can be generated

	public String getAddressLine() {
		return addressLine;
	}

	public String getCity() {
		return city;
	}

	// look how the field's type matches the getter's type;
	// nice for bean-based code/tools

	public Optional<String> getPostcode() {
		return postcode;
	}

}

这里根本没有空值。

差异性

约束问题

在对象内,开发人员仍然*考虑null并使用!= null检查对其进行管理。 这是合理的,因为null问题受到了限制。 代码将全部作为一个单元编写和测试(您确实编写测试不是吗?),因此null不会引起很多问题。

您看到他的构造函数如何允许其中一个参数为null吗? 找出哪一个需要您离开正在做的事情并查看其他类的代码的唯一方法。 这不是什么大事,但是没有必要。

即使撇开这一点,问题也没有受到应有的限制。 假设每个人都不喜欢注释 ,我们必须假设它们不存在,这使构造函数内部和getter的返回类型告诉您该字段可为空。 这不是让您跳出来的最佳信息。

很明显可选很明显

public class Address {

	// look ma, no comments required

	private final String addressLine;
	private final String city;
	private Optional<String> postcode;

	// nobody has to look at these constructors to check which parameters are
	// allowed to be null because of course none are!

	public Address(String addressLine, String city, Optional<String> postcode) {
		this.addressLine = requireNonNull(addressLine,
				"The argument 'addressLine' must not be null.");
		this.city = requireNonNull(city,
				"The argument 'city' must not be null.");
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	public Address(String addressLine, String city, String postcode) {
		// use 'requireNonNull' inside Optional factory method
		// if you prefer a verbose exception message;
		// otherwise 'Optional.of(postcode)' suffices
		this(addressLine, city, Optional.of(
				requireNonNull(postcode,
						"The argument 'postcode' must not be null.")));
	}

	public Address(String addressLine, String city) {
		this(addressLine, city, Optional.empty());
	}

	// now if some method needs to use the postcode,
	// we can not overlook the fact that it is optional

	public int comparePostcode(Address other) {
		// without Optionals we might overlook that the postcode
		// could be missing and do this:
		// return this.postcode.compareTo(other.postcode);

		if (this.postcode.isPresent() && other.postcode.isPresent())
			return this.postcode.get().compareTo(other.postcode.get());
		else if (this.postcode.isPresent())
			return 1;
		else if (other.postcode.isPresent())
			return -1;
		else
			return 0;
	}

	// of course methods that might not have a result
	// return 'Optional' instead of null

	public static Optional<Address> findAddress(String userInput) {
		// find the address, returning Optional.empty() if not found
	}

	// getters are straight forward and can be generated

	public String getAddressLine() {
		return addressLine;
	}

	public String getCity() {
		return city;
	}

	// look how the field's type matches the getter's type;
	// nice for bean-based code/tools

	public Optional<String> getPostcode() {
		return postcode;
	}

	// in case this 'Address' is mutable
	// (which it probably shouldn't be but let's presume it is)
	// you can decide whether you prefer a setter that takes an 'Optional',
	// a pair of methods to set an existing and an empty postcode, or both

	public void setPostcode(Optional<String> postcode) {
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	public void setPostcode(String postcode) {
		// again you might want to use 'requireNonNull'
		// if you prefer a verbose exception message;
		this.postcode = Optional.of(
				requireNonNull(postcode,
						"The argument 'postcode' must not be null."));
	}

	public void setEmptyPostcode() {
		this.postcode = Optional.empty();
	}

}

他的测试论据可能会被数字打断。 如果所有测试都包含所有字段,则每个可选字段将使测试数量加倍,因为对于空和非空情况都应运行每个测试。 我更喜欢将类型系统作为第一道防线。

另一方面,这种痛苦可能会说服开发人员在单个类中找到具有较少可选项的解决方案。

性能

Stephen正确指出,为方法返回值创建的实例然后被快速丢弃(这对于Optional的使用是典型的),几乎没有成本。 与Optional字段不同,后者在整个包含对象的整个生命周期中都存在,并增加了从该对象到Optional的有效负载的间接附加层。

对他来说,这是更喜欢null的原因。

虽然很容易断言这是“过早的优化”,但作为工程师,我们有责任了解所使用系统的限制和功能,并仔细选择应强调的点。

我同意。 但是对我来说,谨慎选择的一部分意味着要首先进行概要介绍。 而且,如果有人向我展示令人信服的论点,即在他的具体情况下,将某些Optional字段替换为可为空的字段会导致明显的性能提升,那么我会立即删除它们的愚蠢框。 但是在所有其他情况下,我坚持使用我认为更易于维护的代码。

顺便说一句,可以为使用数组而不是ArrayLists或使用char-arrays而不是字符串提供相同的参数。 我敢肯定,没有明显的性能提升,没有人会遵循该建议。

但是,讨论中的该重复主题值得关注。 我将尝试寻找一些时间来介绍一些我认为很有趣的用例。

可序列化

尽管这只是次要点,但应注意,该类可以是可序列化的,如果任何字段是Optional的,则这是不可能的(因为Optional不实现Serializable)。

我认为这是可以解决的 但是,这会导致一些额外的工作。

方便

我的经验是,在设置程序或构造函数上使用Optional对调用者很烦,因为它们通常具有实际的对象。 强制调用者将参数包装在Optional中是一种麻烦,我希望不要对用户造成影响。 (即便利性胜过输入的严格性)

虽然编写令人讨厌的代码可能很有趣,但我明白了他的观点。 所以不要强迫用户, 重载您的方法

重载构造函数以避免创建可选项

public class Address {

	// look ma, no comments required

	private final String addressLine;
	private final String city;
	private Optional<String> postcode;

	// nobody has to look at these constructors to check which parameters are
	// allowed to be null because of course none are!

	public Address(String addressLine, String city, Optional<String> postcode) {
		this.addressLine = requireNonNull(addressLine,
				"The argument 'addressLine' must not be null.");
		this.city = requireNonNull(city,
				"The argument 'city' must not be null.");
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	public Address(String addressLine, String city, String postcode) {
		// use 'requireNonNull' inside Optional factory method
		// if you prefer a verbose exception message;
		// otherwise 'Optional.of(postcode)' suffices
		this(addressLine, city, Optional.of(
				requireNonNull(postcode,
						"The argument 'postcode' must not be null.")));
	}

	public Address(String addressLine, String city) {
		this(addressLine, city, Optional.empty());
	}

	// now if some method needs to use the postcode,
	// we can not overlook the fact that it is optional

	public int comparePostcode(Address other) {
		// without Optionals we might overlook that the postcode
		// could be missing and do this:
		// return this.postcode.compareTo(other.postcode);

		if (this.postcode.isPresent() && other.postcode.isPresent())
			return this.postcode.get().compareTo(other.postcode.get());
		else if (this.postcode.isPresent())
			return 1;
		else if (other.postcode.isPresent())
			return -1;
		else
			return 0;
	}

	// of course methods that might not have a result
	// return 'Optional' instead of null

	public static Optional<Address> findAddress(String userInput) {
		// find the address, returning Optional.empty() if not found
	}

	// getters are straight forward and can be generated

	public String getAddressLine() {
		return addressLine;
	}

	public String getCity() {
		return city;
	}

	// look how the field's type matches the getter's type;
	// nice for bean-based code/tools

	public Optional<String> getPostcode() {
		return postcode;
	}

	// in case this 'Address' is mutable
	// (which it probably shouldn't be but let's presume it is)
	// you can decide whether you prefer a setter that takes an 'Optional',
	// a pair of methods to set an existing and an empty postcode, or both

	public void setPostcode(Optional<String> postcode) {
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	public void setPostcode(String postcode) {
		// again you might want to use 'requireNonNull'
		// if you prefer a verbose exception message;
		this.postcode = Optional.of(
				requireNonNull(postcode,
						"The argument 'postcode' must not be null."));
	}

	public void setEmptyPostcode() {
		this.postcode = Optional.empty();
	}

}

当然,这在许多可选字段中无法很好地扩展。 在这种情况下,构建器模式会有所帮助。

事实是,如果我们的可为空的邮政编码中有一个setter,则处理其他代码的开发人员必须再次停止并查看此类以确定她是否可以传递null。 而且由于她永远不能确定,因此她也必须检查其他吸气剂。 谈论烦人的代码...

使用Optional类型的字段,setter可能如下所示:

重载的二传手,避免创建可选项

public class Address {

	// look ma, no comments required

	private final String addressLine;
	private final String city;
	private Optional<String> postcode;

	// nobody has to look at these constructors to check which parameters are
	// allowed to be null because of course none are!

	public Address(String addressLine, String city, Optional<String> postcode) {
		this.addressLine = requireNonNull(addressLine,
				"The argument 'addressLine' must not be null.");
		this.city = requireNonNull(city,
				"The argument 'city' must not be null.");
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	public Address(String addressLine, String city, String postcode) {
		// use 'requireNonNull' inside Optional factory method
		// if you prefer a verbose exception message;
		// otherwise 'Optional.of(postcode)' suffices
		this(addressLine, city, Optional.of(
				requireNonNull(postcode,
						"The argument 'postcode' must not be null.")));
	}

	public Address(String addressLine, String city) {
		this(addressLine, city, Optional.empty());
	}

	// now if some method needs to use the postcode,
	// we can not overlook the fact that it is optional

	public int comparePostcode(Address other) {
		// without Optionals we might overlook that the postcode
		// could be missing and do this:
		// return this.postcode.compareTo(other.postcode);

		if (this.postcode.isPresent() && other.postcode.isPresent())
			return this.postcode.get().compareTo(other.postcode.get());
		else if (this.postcode.isPresent())
			return 1;
		else if (other.postcode.isPresent())
			return -1;
		else
			return 0;
	}

	// of course methods that might not have a result
	// return 'Optional' instead of null

	public static Optional<Address> findAddress(String userInput) {
		// find the address, returning Optional.empty() if not found
	}

	// getters are straight forward and can be generated

	public String getAddressLine() {
		return addressLine;
	}

	public String getCity() {
		return city;
	}

	// look how the field's type matches the getter's type;
	// nice for bean-based code/tools

	public Optional<String> getPostcode() {
		return postcode;
	}

	// in case this 'Address' is mutable
	// (which it probably shouldn't be but let's presume it is)
	// you can decide whether you prefer a setter that takes an 'Optional',
	// a pair of methods to set an existing and an empty postcode, or both

	public void setPostcode(Optional<String> postcode) {
		this.postcode = requireNonNull(postcode,
				"The argument 'postcode' must not be null.");
	}

	public void setPostcode(String postcode) {
		// again you might want to use 'requireNonNull'
		// if you prefer a verbose exception message;
		this.postcode = Optional.of(
				requireNonNull(postcode,
						"The argument 'postcode' must not be null."));
	}

	public void setEmptyPostcode() {
		this.postcode = Optional.empty();
	}

}

同样,所有空值都将立即被例外回答。

豆子

不利的一面是,这种方法导致的对象不是bean。

是的 具有Optional类型的字段不会因此受到影响。

共同点

我们在这里讨论细节不容忽视。 我们的目标是相同的,并且我们提出了类似的实现目标的方法。

如果在应用程序中广泛使用,则null问题趋于消失而无需付出很大的努力。 由于每个域对象都拒绝返回null,因此应用程序往往永远不会传递null。 以我的经验,采用这种方法往往会导致在类的私有范围之外从未使用过null的代码。 重要的是,这自然而然地发生了,而不是一个痛苦的过渡。 随着时间的流逝,您开始编写防御性较低的代码,因为您更有信心没有任何变量实际包含null。

这是一个伟大的目标! 并遵循斯蒂芬的建议将带给您大部分帮助。 因此,不要以我的不同意为理由,至少不使用Optional。

我要说的是,我几乎没有理由停止更多地禁止null!

反射

每当有可为空的内容时,我就解决一些问题并希望驳斥一些反对使用Optional的论点。 我希望表明我更严格的方法在驱散null方面做得更好。 这应该使您有更多的精力去思考更多相关的问题。

付出的代价可能会降低性能。 如果有人证明更多,对于这些特定情况,我们仍然可以返回null。 或将硬件扔在问题上。 或等待值类型

你怎么看?

翻译自: https://www.javacodegeeks.com/2015/08/java-8-se-optional-a-strict-approach.html