Java 9对可选的补充

哇,人们对Java 9的Stream API增添了 真正的兴趣。 想要更多? 让我们看一下……








可选的

可选::流

这不需要任何解释:

Stream<T> stream();

想到的第一个词是: 终于 最后,我们可以轻松地从可选值流变为当前值流!

给定一个Optional findCustomer(String customerId)我们必须执行以下操作:

public Stream<Customer> findCustomers(Collection<String> customerIds) {
	return customerIds.stream()
		.map(this::findCustomer)
		// now we have a Stream<Optional<Customer>>
		.filter(Optional::isPresent)
		.map(Optional::get);
}

或这个:

public Stream<Customer> findCustomers(Collection<String> customerIds) {
	return customerIds.stream()
		.map(this::findCustomer)
		.flatMap(customer -> customer.isPresent()
			? Stream.of(customer.get())
			: Stream.empty());
}

我们当然可以将其推入实用程序方法中(我希望您这样做了),但是它仍然不是最佳方法。

现在,让Optional实际实现Stream会很有趣,但是

  1. 在设计Optional似乎没有考虑过它,并且
  2. 该船已经航行,因为溪流是懒惰的,而Optional不是。

因此,剩下的唯一选择是添加一个返回零或一个元素流的方法。 这样,我们又有两个选择来实现期望的结果:

public Stream<Customer> findCustomers(Collection<String> customerIds) {
	return customerIds.stream()
		.map(this::findCustomer)
		.flatMap(Optional::stream)
}
 
public Stream<Customer> findCustomers(Collection<String> customerIds) {
	return customerIds.stream()
		.flatMap(id -> findCustomer(id).stream());
}

很难说我喜欢哪个更好-都有优点和缺点-但这是另一篇文章的讨论。 两者看起来都比我们之前要做的要好。

现在,我们可以对Optional进行延迟操作。

很难说我喜欢哪个更好-都有优点和缺点-但这是另一篇文章的讨论。 两者看起来都比我们之前要做的要好。

现在,我们可以对Optional进行延迟操作。
另一个小细节:如果愿意,我们现在可以更轻松地从Optional上的急切操作转移到Stream上的惰性操作。

public List<Order> findOrdersForCustomer(String customerId) {
	return findCustomer(customerId)
		// 'List<Order> getOrders(Customer)' is expensive;
		// this is 'Optional::map', which is eager
		.map(this::getOrders)
		.orElse(new ArrayList<>());
}
 
public Stream<Order> findOrdersForCustomer(String customerId) {
	return findCustomer(customerId)
		.stream()
		// this is 'Stream::map', which is lazy
		.map(this::getOrders)
}

我想我还没有用例,但是记住这一点很好。

Java 9对可选的补充

Leo Leung在CC-BY 2.0下发布。

可选::或

最后让我思考的另一个补充! 您多久使用一次Optional并想表达“使用此选项; 除非它是空的,否则在这种情况下我要使用另一个”? 很快我们就可以做到:

Optional<T> or(Supplier<Optional<T>> supplier);

假设我们需要一些客户数据,这些数据通常是从远程服务获得的。 但是因为访问它很昂贵并且非常聪明,所以我们有一个本地缓存。 实际上有两个,一个在内存上,一个在磁盘上。 (我可以看到你畏缩。放松,这只是一个例子。)

这是我们的本地API:

public interface Customers {
 
	Optional<Customer> findInMemory(String customerId);
 
	Optional<Customer> findOnDisk(String customerId);
 
	Optional<Customer> findRemotely(String customerId);
 
}

在Java 8中将这些调用链接起来很麻烦(如果您不相信我,请尝试一下)。 但是使用Optional::or成为小菜一碟:

public Optional<Customer> findCustomer(String customerId) {
	return customers.findInMemory(customerId)
		.or(() -> customers.findOnDisk(customerId))
		.or(() -> customers.findRemotely(customerId));
}

那不是很酷吗? 没有它,我们怎么生活? 勉强可以告诉你。 只是勉强。

可选的:: ifPresentOrElse

对于最后一个,我不太满意:

void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);

您可以使用它来覆盖isPresent -if的两个分支:

public void logLogin(String customerId) {
	findCustomer(customerId)
		.ifPresentOrElse(
			this::logLogin,
			() -> logUnknownLogin(customerId)
		);
}

logLogin超载并且还带了一个客户,然后记录了其登录名。 同样, logUnknownLogin记录未知客户的ID。

现在,我为什么不喜欢它? 因为它迫使我同时执行这两项操作,并且使我无法再进行进一步的链接。 我本来会更愿意这样做:

Optional<T> ifPresent(Consumer<? super T> action);
 
Optional<T> ifEmpty(Runnable action);

上面的情况看起来类似,但更好:

public void logLogin(String customerId) {
	findCustomer(customerId)
		.ifPresent(this::logLogin)
		.ifEmpty(() -> logUnknownLogin(customerId));
}

首先,我发现它更具可读性。 其次,它允许我只拥有ifEmpty分支(如果我愿意的话)(而不会因空lambda而使我的代码混乱)。 最后,它允许我进一步链接这些呼叫。 要继续上面的示例:

public Optional<Customer> findCustomer(String customerId) {
	return customers.findInMemory(customerId)
		.ifEmpty(() -> logCustomerNotInMemory(customerId))
		.or(() -> customers.findOnDisk(customerId))
		.ifEmpty(() -> logCustomerNotOnDisk(customerId))
		.or(() -> customers.findRemotely(customerId))
		.ifEmpty(() -> logCustomerNotOnRemote(customerId))
		.ifPresent(ignored -> logFoundCustomer(customerId));
}

剩下的问题如下:将返回类型添加到方法(在这种情况下为Optional::ifPresent )是否是不兼容的更改? 不太明显,但我目前懒得去调查。 你知道吗?

反射

把它们加起来:

  • 使用Optional::stream将Optional映射到Stream
  • 使用Optional::or将空的Optional替换为返回另一个Optional的调用结果。
  • 使用Optional::ifPresentOrElse可以同时执行isPresent-if两个分支。

很酷!

你怎么看? 我敢肯定那里有人仍然会错过他最喜欢的手术。 告诉我怎么回事儿!

翻译自: https://www.javacodegeeks.com/2016/06/java-9-additions-optional.html