Java 8提供了一个新的API(称为“流”,Stream),它支持许多处理数据的并行操作,其思路和在数据库查询语言中的思路类似——用更高级的方式表达想要的东西,而由“实现”(在这里是Streams库)来选择最佳低级执行机制。这样就可以避免用synchronized编写代码,这一代码不仅容易出错,而且在多核CPU上执行所需的成本也比你想象的要高。
在Java 8中加入Streams可以看作把另外两项扩充加入Java 8的直接原因:把代码传递给方法的简洁方式(方法引用、Lambda)和接口中的默认方法。它提供了一种新的方式,这种方式简洁地表达了行为参数化。
比方说,你想要写两个只有几行代码不同的方法,那现在你只需要把不同的那部分代码作为参数传递进去就可以了.采用这
种编程技巧,代码会更短、更清晰,也比常用的复制粘贴更不容易出错。这比在Java 8之前可以用匿名类实现行为参数化的方式还要简单,后面我们会举例说明.
Java 8里面将代码传递给方法的功能(同时也能够返回代码并将其包含在数据结构中)还让我们能够使用一整套新技巧,通常称为函数式编程。一言以蔽之,这种被函数式编程界称为函数的代码,可以被来回传递并加以组合,以产生强大的编程语汇。
第一个编程概念是流处理。介绍一下,流是一系列数据项,一次只生成一项。程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能是另一个程序的输入流。基于这一思想,Java 8在java.util.stream中添加了一个Stream API;Stream
推动这种做法的关键在于,现在你可以在一个更高的抽象层次上写Java 8程序了:思路变成了把这样的流变成那样的流(就像写数据库查询语句时的那种思路),而不是一次只处理一个项目。另一个好处是,Java 8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行你的Stream操作流水线——这是几乎免费的并行,用不着去费劲搞Thread了。
Java 8中增加的另一个编程概念是通过API来传递代码的能力。这听起来实在太抽象了。比方说,你有一堆发票代码,格式类似于2013UK0001、2014US0002……前四位数代表年份,接下来两个字母代表国家,最后四位是客户的代码。你可能想按照年份、客户代码,甚至国家来对发票进行排序。你真正想要的是,能够给sort命令一个参数让用户定义顺序:给sort命令传递一段独立代码。
那么,直接套在Java上,你是要让sort方法利用自定义的顺序进行比较。你可以写一个compareUsingCustomerId来比较两张发票的代码,但是在Java 8之前,你没法把这个方法传给另一个方法。你可以创建一个Comparator对象,将之传递给sort方法,但这不但啰嗦,而且让“重复使用现有行为”的思想变得不那么清楚了。Java 8增加了把方法(你的代码)作为参数传递给另一个方法的能力
图1-3描绘了这种思路。我们把这一概念称为行为参数化。它的重要之处在哪儿呢?Stream API就是构建在通过传递代码使操作行为实现参数化的思想上的,当把compareUsingCustomerId传进去,你就把sort的行为参数化了。
第三个编程概念更隐晦一点,它来自我们前面讨论流处理能力时说的“几乎免费的并行”。你需要放弃什么吗?你可能需要对传给流方法的行为的写法稍作改变。这些改变可能一开始会让你感觉有点儿不舒服,但一旦习惯了你就会爱上它们。你的行为必须能够同时对不同的输入安全地执行。一般情况下这就意味着,你写代码时不能访问共享的可变数据。这些函数有时被称为“纯函数”或“无副作用函数”或“无状态函数”。。
前面说的并行只有在假定你的代码的多个副本可以独立工作时才能进行。但如果要写入的是一个共享变量或对象,这就行不通了:如果两个进程需要同时修改这个共享变量怎么办?
Java 8的流实现并行比Java现有的线程API更容易,因此,尽管可以使用synchronized来打破“不能有共享的可变数据”这一规则,但这相当于是在和整个体系作对,因为它使所有围绕这一规则做出的优化都失去意义了。在多个处理器内核之间使用synchronized,其代价往往比你预期的要大得多,因为同步迫使代码按照顺序执行,而这与并行处理的宗旨相悖。
这两个要点(没有共享的可变数据,将方法和函数即代码传递给其他方法的能力)是我们平常所说的函数式编程范式的基石.与此相反,在命令式编程范式中,你写的程序则是一系列改变状态的指令。“不能有共享的可变数据”的要求意味着,一个方法是可以通过它将参数值转换为结果的方式完全描述的;换句话说,它的行为就像一个数学函数,没有可见的副作用。
编程语言中的函数一词通常是指方法,尤其是静态方法;这是在数学函数,也就是没有副作用的函数之外的新含义。幸运的是,你将会看到,在Java 8谈到函数时,这两种用法几乎是一致的。
Java 8中新增了函数——值的一种新形式。有了它,Java 8可以进行多核处理器上的并行编程。我们首先来展示一下作为值的函数本身的有用之处。想想Java程序可能操作的值吧。首先有原始值,比如42(int类型)和3.14(double类型)。其次,值可以是对象(更严格地说是对象的引用)。获得对象的唯一途径是利用new,也许是通过工厂方法或库函数实现的;对象引用指向类的一个实例。
例子包括"abc"(String类型),new Integer(1111)(Integer类型),以及new HashMap
为了帮助回答这个问题,我们要注意到,编程语言的整个目的就在于操作值,要是按照历史上编程语言的传统,这些值因此被称为一等值(或一等公民,这个术语是从20世纪60年代美国民权运动中借用来的)。编程语言中的其他结构也许有助于我们表示值的结构,但在程序执行期间不能传递,因而是二等公民。前面所说的值是Java中的一等公民,但其他很多Java概念(如方法和类等)则是二等公民。用方法来定义类很不错,类还可以实例化来产生值,但方法和类本身都不是值。这又有什么关系呢?还真有,人们发现,在运行时传递方法能将方法变成一等公民。这在编程中非常有用,因此Java 8的设计者把这个功能加入到了Java中。顺便说一下,你可能会想,让类等其他二等公民也变成一等公民可能也是个好主意。有很多语言,如Smalltalk和JavaScript,都探索过这条路。