一、第一章
1、了解概念
a、Stream API
b、向方法传递代码的技巧。
c、接口中的默认方法。
1.1、
在java.util.stream中添加了一个Stream API;Stream就是一 系列T类型的项目。可以把它看成一种比较花哨的迭代器。Stream API的很多方法可以链 接起来形成一个复杂的流水线,推动这种做法的关键在于,现在你可以在一个更高的抽象层次上写Java 8程序了:思路变成 了把这样的流变成那样的流(就像写数据库查询语句时的那种思路),而不是一次只处理一个项 目。
另一个好处是,Java 8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行你的 Stream操作流水线——这是几乎免费的并行,用不着去费劲搞Thread了。
1.2、
并行与共享的可变数据
写代码时不能访问共享的可变数据。这些函数有时被称为“纯 函数”或“无副作用函数”或“无状态函数”,如果要写入的是一个共享变 量或对象,这就行不通了:如果两个进程需要同时修改这个共享变量怎么办? Java 8的流实现并行比Java现有的线程API更容易,因此,尽管可以使用synchronized来打 破“不能有共享的可变数据”这一规则,但这相当于是在和整个体系作对,因为它使所有围绕这 一规则做出的优化都失去意义了。在多个处理器内核之间使用synchronized,其代价往往比你 预期的要大得多,因为同步迫使代码按照顺序执行,而这与并行处理的宗旨相悖。 这两个要点(没有共享的可变数据,将方法和函数即代码传递给其他方法的能力)是我们平 常所说的函数式编程范式的基石。在命令式编程 范式中,写的程序则是一系列改变状态的指令。“不能有共享的可变数据”的要求意味着,一 个方法是可以通过它将参数值转换为结果的方式完全描述的;换句话说,它的行为就像一个数学 函数,没有可见的副作用。
1.3、方法作为值传递
Java 8的第一个新功能是方法引用。比方说,你想要筛选一个目录中的所有隐藏 文件。你需要编写一个方法,然后给它一个File,它就会告诉你文件是不是隐藏的。
幸好,File 类里面有一个叫作isHidden的方法。我们可以把它看作一个函数,接受一个File,返回一个布 尔值。但要用它做筛选,你需要把它包在一个FileFilter对象里,然后传递给File.listFiles 方法,如下所示:
File[] hiddenFiles = new File(".").listFiles(newFileFilter() {publicboolean accept(File file) {returnfile.isHidden();
}
});
如今在Java 8里,你可以把代码重写成这个样子:
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
已经有了函数isHidden,因此只需用Java 8的方法引用::语法(即“把这 个方法作为值”)将其传给listFiles方法;开始用函数代表方法了。一个好处是,代码现在读起来更接近问题的陈述了。方法不再是二等值了。与用对象引用传递对象类似(对象引用是用new创建的),在Java 8里写下 File::isHidden的时候,就创建了一个方法引用,同样可以传递它。
Lambda——匿名函数
除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值的思想,包括 Lambda(或匿名函数)。比如,可以写(int x) -> x + 1,表示“调用时给定参数x, 就返回x + 1值的函数”。可能会想这有什么必要呢?因为可以在MyMathsUtils类里面定义 一个add1方法,然后写MyMathsUtils::add1嘛!确实是可以,但要是没有方便的方法和类可用,新的Lambda语法更简洁。这句话的意思是“编写把函数作为一等值来传递的程序”。
1.4、默认方法
jdk1.8之后,接口可以写方法的实现,如List这个接口,实现了默认方法,需要用default关键字来声明
default void sort(Comparator super E>c) {
Object[] a= this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator i = this.listIterator();for(Object e : a) {
i.next();
i.set((E) e);
}
}
加入默认方法主要是为了支持库设计师,让他们能够写出更容易改进的接口,多用于程序的改进。
二、第二章
使用lambda表达式实现代码实例,跟以往用类、匿名内部类的对比。
public classFilteringApples {public static voidmain(String[] args) {
List inventory= Arrays.asList(new Apple(80,"green"),new Apple(155,"green"),new Apple(120,"red"));//[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}]
List apples0 =FilteringApples.fileterApples(inventory, FilteringApples::isGreenApple);
System.out.println(apples0);//[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}]
List apples1 = FilteringApples.fileterApples(inventory, (Apple a) -> "green".equals(a.getColor()));
System.out.println(apples1);//[Apple{weight=155, color='green'}, Apple{weight=120, color='red'}]
List apples2 =FilteringApples.fileterApples(inventory,
(Apple a)-> "red".equals(a.getColor()) || a.getWeight()>150);
System.out.println(apples2);
}public static List fileterApples(List inventory, Predicatep){
List result=new ArrayList<>();for(Apple apple:inventory){if(p.test(apple)){
result.add(apple);
}
}returnresult;
}public staticboolean isGreenApple(Apple apple){return "green".equals(apple.getColor());
}public static List filterGreenApples(Listinventory){
List result=new ArrayList<>();for(Apple apple:inventory){if("green".equals(apple.getColor())){
result.add(apple);
}
}returnresult;
}static classApple{private int weight=0;private String color="";
Apple(inti,String color){
weight=i;this.color=color;
}public intgetWeight() {returnweight;
}public void setWeight(intweight) {this.weight =weight;
}publicString getColor() {returncolor;
}public voidsetColor(String color) {this.color =color;
}
@OverridepublicString toString() {return "Apple{" +
"weight=" + weight +
", color='" + color + '\'' +
'}';
}
}
}
以前比较繁琐的方式:
public classFilteringApples {public static voidmain(String[] args) {
List inventory= Arrays.asList(new Apple(80,"green"),new Apple(155,"green"),new Apple(120,"red"));//[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}]
List list1 = FilteringApples.filter(inventory, newColorApplePredicate());
System.out.println(list1);//[Apple{weight=155, color='green'}]
List list2 = FilteringApples.filter(inventory, newWeightApplePredicate());
System.out.println(list2);//[Apple{weight=155, color='green'}, Apple{weight=120, color='red'}]//使用匿名类
List list3 = FilteringApples.filter(inventory, newApplePredicate(){
@Overridepublicboolean check(Apple apple) {return apple.getWeight()>150 || "red".equals(apple.getColor());
}
});
System.out.println(list3);
}//体现了策略模式,想根据什么筛选就传递什么实现类
public static List filter(Listinventory ,ApplePredicate applePredicate){
List result=new ArrayList<>();for(Apple apple:inventory){if(applePredicate.check(apple)){
result.add(apple);
}
}returnresult;
}interfaceApplePredicate{
boolean check(Apple apple);
}static classColorApplePredicate implements ApplePredicate{
@Overridepublicboolean check(Apple apple) {return "green".equals(apple.getColor());
}
}static classWeightApplePredicate implements ApplePredicate{
@Overridepublicboolean check(Apple apple) {return apple.getWeight()>150;
}
}static classApple{private int weight=0;private String color="";
Apple(inti,String color){
weight=i;this.color=color;
}public intgetWeight() {returnweight;
}public void setWeight(intweight) {this.weight =weight;
}publicString getColor() {returncolor;
}public voidsetColor(String color) {this.color =color;
}
@OverridepublicString toString() {return "Apple{" +
"weight=" + weight +
", color='" + color + '\'' +
'}';
}
}
}
三、第三章
1、lambda表达式有三部分
a、参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。
b、箭头——箭头->把参数列表与Lambda主体分隔开。
c、Lambda主体——比较两个Apple的重量。表达式就是Lambda的返回值了。
2、Java8中有效的Lambda表达式
3、测验,判断下面Lambda语法是否正确
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}
答案:只有4和5是无效的Lambda。
(1) 这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。
(2) 这个Lambda没有参数,并返回String作为表达式。
(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。
(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示: (Integer i) -> {return "Alan" + i;}。 (5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号 和分号,如下所示:(String s) -> "Iron Man"。或者如果你喜欢,可以使用显式返回语 句,如下所示:(String s)->{return "IronMan";}。
4、Lambda示例
5、函数式接口
函数式接口就是只定义一个抽象方法的接口。哪怕接口有很多默认方法,只要接口只定义一个抽象方法,它仍然是一个函数式接口。现函数式接口带有@FunctionalInterface的标注。
作用:Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,
具体说来,是函数式接口一个具体实现的实例。
用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,
然后再直接内联将它实例化。下面的代码是有效的,因为Runnable是一个只定义了一个抽象方法run的函数式接口:
6、jdk 中Predicate、Consumer、Function三个函数式接口
a、Predicate
java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型 T对象,并返回一个boolean。在需要 表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String 对象的Lambda表达式,如下所示。
@FunctionalInterfacepublic interface Predicate{
boolean test(T t);
}public static List filter(List list, Predicatep) {
List results = new ArrayList<>();for(T s: list){if(p.test(s)){
results.add(s);
}
}returnresults;
}
Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
b、Consumer
java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T 的对象,没有返回(void)。需要访问类型T的对象,并对其执行某些操作,就可以使用 这个接口。
@FunctionalInterfacepublic interface Consumer{voidaccept(T t);
}public static void forEach(List list, Consumerc){for(T i: list){
c.accept(i);
}
}
forEach(Arrays.asList(1,2,3,4,5),
(Integer i)-> System.out.println(i)
);
c、Function
java.util.function.Function接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。如果需要定义一个Lambda,将输入对象的信息映射 到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。在下面的代码中,展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个 String长度的Integer列表。
@FunctionalInterfacepublic interface Function{
R apply(T t);
}public static List map(Listlist,
Functionf) {
List result = new ArrayList<>();for(T s: list){
result.add(f.apply(s));
}returnresult;
}//[7, 2, 6]
List l =map(
Arrays.asList("lambdas","in","action"),
(String s)->s.length()
);
其他一些函数式接口:
最主要的一点是:如果需要什么样的类型,输出什么样的类型,都可以自己设计一个函数式接口来用。
7、方法引用
显式地指明方法的名称,代码的可读性会更好。它是如何工作的呢? 当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。
方法引用主要有三类:
(1) 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
(2) 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作 String::length)。
(3) 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction 用于存放Transaction类型的对对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。
第二种和第三种方法引用可能乍看起来有点儿晕。
类似于String::length的第二种方法引 用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。
例如,Lambda 表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。
但第三种方法引用 指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式 ()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。
依照一些简单的方子,我们就可以将Lambda表达式重构为等价的方法引用,如图3-5所示。
看下面一个排序的例子,怎么一步步演化成简单的写法
//1
List inventory = new ArrayList<>();
inventory.addAll(Arrays.asList(new Apple(80,"green"), new Apple(155, "green"), new Apple(120, "red")));//[Apple{color='green', weight=80}, Apple{color='red', weight=120}, Apple{color='green', weight=155}]
inventory.sort(newAppleComparator());
System.out.println(inventory);//改一下第2位置的元素
inventory.set(1, new Apple(30, "green"));//2//[Apple{color='green', weight=30}, Apple{color='green', weight=80}, Apple{color='green', weight=155}]
inventory.sort(new Comparator() {public intcompare(Apple a1, Apple a2){returna1.getWeight().compareTo(a2.getWeight());
}});
System.out.println(inventory);//改一下第2位置的元素
inventory.set(1, new Apple(20, "red"));//3//[Apple{color='red', weight=20}, Apple{color='green', weight=30}, Apple{color='green', weight=155}]
inventory.sort((a1, a2) ->a1.getWeight().compareTo(a2.getWeight()));
System.out.println(inventory);//改一下第2位置的元素
inventory.set(1, new Apple(10, "red"));//4//[Apple{color='red', weight=10}, Apple{color='red', weight=20}, Apple{color='green', weight=155}]
inventory.sort(comparing(Apple::getWeight));
//inventory.sort(comparing(Apple::getWeight).reversed()); //逆序
System.out.println(inventory);
Comparator具有一个叫作comparing的静态辅助方法, 它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。
最终的代码读起来的意思就是:对库存进行排序,比较苹果的重量。