Java8的新特性:Lambda(匿名函数)流/默认方法(下)
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java8的新特性:Lambda(匿名函数)流/默认方法(下),小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含9384字,纯文字阅读大概需要14分钟。
内容图文
![Java8的新特性:Lambda(匿名函数)流/默认方法(下)](/upload/InfoBanner/zyjiaocheng/736/0e1f53ce215b4732add34d75ce7d71bb.jpg)
流
几乎每个Java应用都会制造和处理集合。但集合用起来并不总是那么理想。比方说,你需要从一个列表中筛选金额较高的交易,然后按货币分组。你需要写一大堆套路化的代码来实现这个数据处理命令,如下所示:
外部迭代
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
// 建立累积交易分组的Map
for (Transaction transaction : transactions) {
if(transaction.getPrice() > 1000){
// 筛选金额较高的交易
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency =
transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
// 如果这个货币的分组Map是空的,那就建立一个
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency,
transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
// 将当前遍历的交易添加到具有同一货币的交易List中
}
}
此外,我们很难一眼看出来这些代码是做什么的,因为有好几个嵌套的控制流指令。有了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));
// 按货币分组
现在值得注意的是,和Collection API相比,Stream API处理数据的方式非常不同。用集合的话,你得自己去做迭代的过程。你得用for-each循环一个个去迭代元素,然后再处理元素。我们把这种数据迭代的方法称为外部迭代。相反,有了Stream API,你根本用不着操心循环的事情。数据处理完全是在库内部进行的。我们把这种思想叫作内部迭代。使用集合的另一个头疼的地方是,想想看,要是你的交易量非常庞大,你要怎么处理这个巨大的列表呢?单个CPU根本搞不定这么大量的数据,但你很可能已经有了一台多核电脑。理想的情况下,你可能想让这些CPU内核共同分担处理工作,以缩短处理时间。理论上来说,要是你有八个核,那并行起来,处理数据的速度应该是单核的八倍。
多核
所有新的台式和笔记本电脑都是多核的。它们不是仅有一个CPU,而是有四个、八个,甚至更多CPU,通常称为内核①。问题是,经典的Java程序只能利用其中一个核,其他核的处理能力都浪费了。类似地,很多公司利用计算集群(用高速网络连接起来的多台计算机)来高效处理海量数据。Java 8提供了新的编程风格,可更好地利用这样的计算机。Google的搜索引擎就是一个无法在单台计算机上运行的代码的例子。它要读取互联网上的每个页面并建立索引,将每个互联网网页上出现的每个词都映射到包含该词的网址上。然后,如果你用多个单词进行搜索,软件就可以快速利用索引,给你一个包含这些词的网页集合。想想看,你会如何在Java中实现这个算法,哪怕是比Google小的引擎也需要你利用计算机上所有的核。
Java8如何解决多线程访问出现的问题
Java 8也用Stream API(java.util.stream)解决了这两个问题:集合处理时的套路和晦涩,以及难以利用多核。这样设计的第一个原因是,有许多反复出现的数据处理模式,类似于前一节所说的filterApples或SQL等数据库查询语言里熟悉的操作,如果在库中有这些就会很方便:根据标准筛选数据(比如较重的苹果),提取数据(例如抽取列表中每个苹果的重量字段),或给数据分组(例如,将一个数字列表分组,奇数和偶数分别列表)等。第二个原因是,这类操作常常可以并行化。例如在两个CPU上筛选列表,可以让一个CPU处理列表的前一半,第二个CPU处理后一半,这称为分支步骤(1)。CPU随后对各自的半个列表做筛选(2)。最后(3),一个CPU会把两个结果合并起来(Google搜索这么快就与此紧密相关,当然他们用的CPU远远不止两个了)。
新的Stream API和Java现有的集合API的行为差不多:它们都能够访问数据项目的序列。不过,现在最好记得,Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的计算。这里的关键点在于,Stream允许并提倡并行处理一个Stream中的元素。虽然可能乍看上去有点儿怪,但筛选一个Collection(将上一节的filterApples应用在一个List上)的最快方法常常是将其转换为Stream,进行并行处理,然后再转换回List,下面举的串行和并行的例子都是如此。我们这里还只是说“几乎免费的并行”,让你稍微体验一下,如何利用Stream和Lambda表达式顺序或并行地从一个列表里筛选比较重的苹果。
import static java.util.stream.Collectors.toList;
List<Apple> heavyApples =inventory.stream().filter((Apple a) -> a.getWeight() > 150)
.collect(toList());
import static java.util.stream.Collectors.toList;
List<Apple> heavyApples =inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150)
.collect(toList());
Java 8 设计者发现的一个现实问题就是现有的接口也在改进。比如,Collections.sort方法真的应该属于List接口,但却从来没有放在后者里。理想的情况下,你会希望做list.sort(comparator),而不是Collections.sort(list, comparator)。这看起来无关紧要,但是在Java 8之前,你可能会更新一个接口,然后发现你把所有实现它的类也给更新了——简直是逻辑灾难!这个问题在Java 8里由默认方法解决了。
Java中的并行与无共享可变状态
大家都说Java里面并行很难,而且和synchronized相关的玩意儿都容易出问题。那Java 8里面有什么“灵丹妙药”呢?事实上有两个。首先,库会负责分块,即把大的流分成几个小的流,以便并行处理。其次,流提供的这个几乎免费的并行,只有在传递给filter之类的库方法的方法不会互动(比方说有可变的共享对象)时才能工作。但是其实这个限制对于程序员来说挺自然的,举个例子,我们的Apple::isGreenApple就是这样。确实,虽然函数式编程中的函数的主要意思是“把函数作为一等值”,不过它也常常隐含着第二层意思,即“执行时在元素之间无互动”。
默认方法
Java 8中加入默认方法主要是为了支持库设计师,让他们能够写出更容易改进的接口。
List<Apple> heavyApples1 =inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(toList());
List<Apple> heavyApples2 =inventory.parallelStream().filter((Apple a) -> a.getWeight() >150).collect(toList());
但这里有个问题:在Java 8之前,List<T>并没有stream或parallelStream方法,它实现的Collection<T>接口也没有,因为当初还没有想到这些方法嘛!可没有这些方法,这些代码就不能编译。换作你自己的接口的话,最简单的解决方案就是让Java 8的设计者把stream方法加入Collection接口,并加入ArrayList类的实现。
可要是这样做,对用户来说就是噩梦了。有很多的替代集合框架都用Collection API实现了接口。但给接口加入一个新方法,意味着所有的实体类都必须为其提供一个实现。语言设计者没法控制Collections所有现有的实现,这下你就进退两难了:你如何改变已发布的接口而不破坏已有的实现呢?
Java 8的解决方法就是打破最后一环——接口如今可以包含实现类没有提供实现的方法签名了!那谁来实现它呢?缺失的方法主体随接口提供了(因此就有了默认实现),而不是由实现类提供。
这就给接口设计者提供了一个扩充接口的方式,而不会破坏现有的代码。Java 8在接口声明中使用新的default关键字来表示这一点。
在Java 8里,你现在可以直接对List调用sort方法。它是用Java 8 List接口中如下所示的默认方法实现的,它会调用Collections.sort静态方法
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
这意味着List的任何实体类都不需要显式实现sort,而在以前的Java版本中,除非提供了sort的实现,否则这些实体类在重新编译时都会失败。不过慢着,一个类可以实现多个接口,不是吗?那么,如果在好几个接口里有多个默认实现,是否意味着Java中有了某种形式的多重继承?是的,在某种程度上是这样。Java 8用一些限制来避免出现类似于C++中臭名昭著的菱形继承问题。
来自函数式编程的其他好思想
前几节介绍了Java中从函数式编程中引入的两个核心思想:将方法和Lambda作为一等值,以及在没有可变共享状态时,函数或方法可以有效、安全地并行执行。前面说到的新的Stream API把这两种思想都用到了。
常见的函数式语言,如SML、OCaml、Haskell,还提供了进一步的结构来帮助程序员。其中之一就是通过使用更多的描述性数据类型来避免null。确实,计算机科学巨擘之一托尼·霍尔(Tony Hoare)在2009年伦敦QCon上的一个演讲中说道:
我把它叫作我的“价值亿万美金的错误”。就是在1965年发明了空引用……我无法抗拒放进一个空引用的诱惑,仅仅是因为它实现起来非常容易。
在Java 8里有一个Optional<T>类,如果你能一致地使用它的话,就可以帮助你避免出现NullPointer异常。它是一个容器对象,可以包含,也可以不包含一个值。Optional<T>中有方法来明确处理值不存在的情况,这样就可以避免NullPointer异常了。换句话说,它使用类型系统, 允许你表明我们知道一个变量可能会没有值。
在Java中,你可以在这里写一个if-then-else语句或一个switch语句。其他语言表明,对于更复杂的数据类型,模式匹配可以比if-then-else更简明地表达编程思想。对于这种数据类型,你也可以使用多态和方法重载来替代if-then-else,但对于哪种方式更合适,就语言设计而言仍有一些争论。②我们认为两者都是有用的工具,你都应该掌握。不幸的是,Java 8对模式匹配的支持并不完全,与此同时,我们会用一个以Scala语言(另一个使用JVM的类Java语言,启发了Java在一些方面的发展)表达的例子加以描述。
比方说,你要写一个程序对描述算术表达式的树做基本的简化。给定一个数据类型Expr代表这样的表达式,在Scala里你可以写以下代码,把Expr分解给它的各个部分,然后返回另一个Expr:
def simplifyExpression(expr: Expr): Expr = expr match {
case BinOp("+", e, Number(0)) => e
case BinOp("*", e, Number(1)) => e
case BinOp("/", e, Number(1)) => e
case _ => expr
}
这里,Scala的语法expr match就对应于Java中的switch (expr)。现在你不用担心这段代码,你可以在第14章阅读更多有关模式匹配的内容。现在,你可以把模式匹配看作switch的扩展形式,可以同时将一个数据类型分解成元素。
为什么Java中的switch语句应该限于原始类型值和Strings呢?函数式语言倾向于允许switch用在更多的数据类型上,包括允许模式匹(在Scala代码中是通过match操作实现的)。在面向对象设计中,常用的访客模式可以用来遍历一组类(如汽车的不同组件:车轮、发动机、底盘等),并对每个访问的对象执行操作。模式匹配的一个优点是编译器可以报告常见错误,如:“Brakes类属于用来表示Car类的组件的一族类。你忘记了要显式处理它。”
总结
Java 8中新增的核心内容提供了令人激动的新概念和功能,方便我们编写既有效又简洁的程序。
现有的Java编程实践并不能很好地利用多核处理器。
函数是一等值;记得方法如何作为函数式值来传递,还有Lambda是怎样写的。
Java 8中Streams的概念使得Collections的许多方面得以推广,让代码更为易读,并允许并行处理流元素。
你可以在接口中使用默认方法,在实现类没有实现方法时提供方法内容。
其他来自函数式编程的有趣思想,包括处理null和使用模式匹配。
内容总结
以上是互联网集市为您收集整理的Java8的新特性:Lambda(匿名函数)流/默认方法(下)全部内容,希望文章能够帮你解决Java8的新特性:Lambda(匿名函数)流/默认方法(下)所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。