哇,人们对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会很有趣,但是
- 在设计
Optional时似乎没有考虑过它,并且 - 该船已经航行,因为溪流是懒惰的,而
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)
}
我想我还没有用例,但是记住这一点很好。
可选::或
最后让我思考的另一个补充! 您多久使用一次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