如想了解 Oracle 公司对 JDK 的最新支持情况,请访问https://www.oracle.com/technetwork/java/java-se-supportroadmap.html。
所有的示例代码均可见于图灵社区本书主页 http://ituring.com.cn/book/2659“随书下载”处。
1.1 为什么要关心 Java 的变化
Java8做的改变很大,并且编程更更容易了,不需要写啰嗦的代码
比如:按照重量给 inventory 中的苹果排序
Collections.sort(inventory, new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
使用Java8后,让代码读起来更接近问题描述本身:inventory.sort(comparing(Apple::getWeight));
从 Java 的演进路径来看,它一直致力于让并发编程更容易、出错更少。Java8会更好的利用cpu核。
特性:
- Stream API;
- 向方法传递代码的技巧;
- 接口的默认方法。
Java 8 提供了一个新的 API(称为“流”,Stream),它支持多个数据处理的并行操作,其思路和数据库查询语言类似。 Java 8 中加入 Stream 可以视为添加另外两项的直接原因:向方法传递代码的简洁技巧(方法引用、Lambda)和接口中的默认方法。它提供了一种新的方式,能够简洁地表达行为参数化。
1.2 Java 怎么还在变
编程语言就像生态系统一样,新的语言会出现,旧语言则被取代,除非它们不断演变。
1.2.1 Java 在编程语言生态系统中的位置
Java它就是一门精心设计的面向对象的语言。提供了大量有用的库。集成的线程和锁的支持,还有jvm虚拟机的平台。
1.2.2 流处理
第一个编程概念是流处理。流是一系列数据项,一次只生成一项。程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能是另一个程序的输入流。
Java 8 可以透明地把输入的不相关部分拿到几个 CPU 核上去分别执行你的Stream 操作流水线——这是几乎免费的并行,用不着去费劲搞 Thread 了
1.2.3 用行为参数化把代码传递给方法
1.2.4 并行与共享的可变数据
Java 8 的流实现并行比 Java 现有的 Thread API 更容易,因此,尽管可以使用 synchronized来打破“不能有共享的可变数据”这一规则,但这相当于是在和整个体系作对,因为它使所有围绕这一规则做出的优化都失去意义了。在多个处理器核之间使用 synchronized,其代价往往比你预期的要大得多,因为同步迫使代码按照顺序执行,而这与并行处理的宗旨相悖。
没有共享的可变数据,以及将方法和函数(即代码)传递给其他方法的能力,这两个要点是函数式编程范式的基石
1.2.5 Java 需要演变
从泛型到for-each,以及各种锁,Java一直在进化。
1.3 Java 中的函数
编程语言中的函数一词通常是指方法,尤其是静态方法,这是在数学函数,也就是没有副作用的函数之外的一个新含义
编程语言的整个目的就在于操作值,按照历史上编程语言的传统,这些值应被称为一等值(或一等公民)。编程语言中的其他结构也许有助于表示值的结构,但在程序执行期间不能传递,因而是二等值。比如方法和类等则是二等值。
用方法来定义类很不错,类还可以实例化来产生值,但方法和类本身都不是值。人们发现,在运行时传递方法能将方法变成一等值,因此 Java 8 的设计者把这个功能加入到了 Java中。
1.3.1 方法和 Lambda 作为一等值
Scala和 Groovy等语言的实践已经证明,让方法等概念作为一等值可以扩充程序员的工具库,从而让编程变得更容易。
我们介绍的 Java 8 的第一个新功能是方法引用。比方说,你想要筛选一个目录中的所有隐藏文件。你需要编写一个方法,然后给它一个 File,判断文件是不是隐藏的。
File类里面有一个叫作 isHidden 的方法。可以把它看作一个函数,接受一个 File,返回一个布尔值。但要用它做筛选,需要把它包在一个 FileFilter 对象里,然后传递给 File.listFiles方法,如下所示:
1.3.2 传递代码:一个例子
一个 Apple 类,它有一个 getColor 方法,还有一个变量 inventory 保存着一个 Apples 列表。你可能想要选出所有的绿苹果(此处使用包含值 GREEN 和 RED 的 Color 枚举类型 ),并返回一个列表。在 Java 8 之前,你可能会写这样一个方法filterGreenApples:
public static List<Apple> filterHeavyApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if (apple.getWeight() > 150) {
result.add(apple);
}
}
return result;
}
与上面的代码的区别在于if条件的不同,基本是复制粘贴。
如果Java 8 会把条件代码作为参数传递进去
public static boolean isGreenApple(Apple apple) {
return GREEN.equals(apple.getColor());
}
public static boolean isHeavyApple(Apple apple) {
return apple.getWeight() > 150;
}
// 写出来是为了清晰(平常只要从java.util.function导入就可以了)
public interface Predicate<T>{
boolean test(T t);
}
static List<Apple> filterApples(List<Apple> inventory,
Predicate<Apple> p) {// 方法作为 Predicate参数 p 传递进去
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if (p.test(apple)) {// 苹果符合 p所代表的条件吗
result.add(apple);
}
return result;
}
}
要用它的话,你可以写:
filterApples(inventory, Apple::isGreenApple);
或者filterApples(inventory, Apple::isHeavyApple);
什么是谓词?
前面的代码传递了方法 Apple::isGreenApple(它接受参数 Apple 并返回一个boolean)给 filterApples,后者则希望接受一个 Predicate参数。谓词(predicate)在数学上常常用来代表类似于函数的东西,它接受一个参数值,并返回 true 或 false。后面你会看到,Java 8 也允许你写 Function<Apple,Boolean>——在学校学过函数却没学过谓词的读者对此可能更熟悉,但用 Predicate是更标准的方式,效率也会更高一点儿,这避免了把 boolean 封装在 Boolean 里面。
1.3.3 从传递方法到 Lambda
如果需要单独写一些isHeavyApple 和 isGreenApple这种就用一两次的方法可能感觉也没那么好,所以就它引入了一套新记法(匿名函数或 Lambda),让你可以写
filterApples(inventory, (Apple a) -> GREEN.equals(a.getColor()) );
或者
filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
甚至
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || RED.equals(a.getColor()) );
这样就不需要定义了。
Java还加了filter和几个相关的东西作为通用库,比如static <T> Collection<T> filter(Collection<T> c, Predicate<T> p);
所以,这样子写就可以了filter(inventory, (Apple a) -> a.getWeight() > 150 );
但是,为了更好地利用并行,Java 的设计师没有这么做。Java 8 中有一整套新的类 Collection API——Stream,它有一套类似于函数式程序员熟悉的 filter 的操作,比如 map、reduce,还有接下来要讨论的在 Collection 和 Stream 之间做转换的方法。
1.4 流
如果你想从从一个列表中筛选金额较高的交易,然后按货币分组。你大概的写一大堆的代码
上面的代码复杂并且几个嵌套的控制流指令
有了Java8,可以这样写
并行处理:List<Apple> heavyApples =inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150).collect(toList());
几乎是“免费”的并行处理。
1.5 默认方法及 Java 模块
一个接口如果想增加一个方法,那么所有实现这个接口的类都要去实现这个方法。工作量太大了。
Java8接口如今可以包含实现类没有提供实现的方法签名了.这就给接口设计者提供了一种扩充接口的方式,而不会破坏现有的代码。Java 8 在接口声明中使用新的 default 关键字来表示这一点。
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
1.6 来自函数式编程的其他好思想
Java 从函数式编程引入的两个核心思想:将方法和 Lambda 作为一等值,以及在没有可变共享状态时,函数或方法可以有效、安全地并行执行
Java 8 提供了一个 Optional类,如果你能一致地使用它,就能帮助你避免出现 NullPointerException。