几乎每个Java应用都会制造和处理集合。但集合用起来并不总是那么理想。比方说,你需要从一个列表中筛选金额较高的交易,然后按货币分组。你需要写一大堆套路化的代码来实现这个数据处理命令,如下所示:
Map<Currency, List<Transaction>> transactionsByCurrencies =
new HashMap<>();
for (Transaction transaction : transactions) {
if(transaction.getPrice() > 1000){
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency =
transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency,
transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}
}
此外,我们很难一眼看出来这些代码是做什么的,因为有好几个嵌套的控制流指令。
有了Stream API,你现在可以这样解决这个问题了:
import static java.util.stream.Collectors.toList;
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream()
.filter((Transaction t) -> t.getPrice() > 1000)
.collect(groupingBy(Transaction::getCurrency));
这看起来有点儿神奇,不过现在先不用担心。第4~7章会专门讲述怎么理解Stream API。现在值得注意的是,和Collection API相比,Stream API处理数据的方式非常不同。用集合的话,你得自己去做迭代的过程。你得用for-each循环一个个去迭代元素,然后再处理元素。我们把这种数据迭代的方法称为外部迭代。相反,有了Stream API,你根本用不着操心循环的事情。数据处理完全是在库内部进行的。我们把这种思想叫作内部迭代。在第4章我们还会谈到这些思想。
使用集合的另一个头疼的地方是,想想看,要是你的交易量非常庞大,你要怎么处理这个巨大的列表呢?单个CPU根本搞不定这么大量的数据,但你很可能已经有了一台多核电脑。理想的情况下,你可能想让这些CPU内核共同分担处理工作,以缩短处理时间。理论上来说,要是你有八个核,那并行起来,处理数据的速度应该是单核的八倍。
多核
所有新的台式和笔记本电脑都是多核的。它们不是仅有一个CPU,而是有四个、八个,甚至更多CPU,通常称为内核 。问题是,经典的Java程序只能利用其中一个核,其他核的处理能力都浪费了。类似地,很多公司利用计算集群(用高速网络连接起来的多台计算机)来高效处理海量数据。Java 8提供了新的编程风格,可更好地利用这样的计算机。
Google的搜索引擎就是一个无法在单台计算机上运行的代码的例子。它要读取互联网上的每个页面并建立索引,将每个互联网网页上出现的每个词都映射到包含该词的网址上。然后,如果你用多个单词进行搜索,软件就可以快速利用索引,给你一个包含这些词的网页集合。想想看,你会如何在Java中实现这个算法,哪怕是比Google小的引擎也需要你利用计算机上所有的核。
多线程并非易事
问题在于,通过多线程代码来利用并行(使用先前Java版本中的Thread API)并非易事。你得换一种思路:线程可能会同时访问并更新共享变量。因此,如果没有协调好 ,数据可能会被意外改变。相比一步步执行的顺序模型,这个模型不太好理解 。比如,图1-5就展示了如果没有同步好,两个线程同时向共享变量sum加上一个数时,可能出现的问题。