2020-04-08 (Java8实战)第一部分
一、第一章
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(new FileFilter() { public boolean accept(File file) { return file.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<E>这个接口,实现了默认方法,需要用default关键字来声明
default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
加入默认方法主要是为了支持库设计师,让他们能够写出更容易改进的接口,多用于程序的改进。
二、第二章
使用lambda表达式实现代码实例,跟以往用类、匿名内部类的对比。
public class FilteringApples { public static void main(String[] args) { List<Apple> 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<Apple> apples0 = FilteringApples.fileterApples(inventory, FilteringApples::isGreenApple); System.out.println(apples0); //[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}] List<Apple> 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<Apple> apples2 = FilteringApples.fileterApples(inventory, (Apple a) -> "red".equals(a.getColor()) || a.getWeight()>150); System.out.println(apples2); } public static List<Apple> fileterApples(List<Apple> inventory, Predicate<Apple> p){ List<Apple> result=new ArrayList<>(); for(Apple apple:inventory){ if(p.test(apple)){ result.add(apple); } } return result; } public static boolean isGreenApple(Apple apple){ return "green".equals(apple.getColor()); } public static List<Apple> filterGreenApples(List<Apple> inventory){ List<Apple> result=new ArrayList<>(); for(Apple apple:inventory){ if("green".equals(apple.getColor())){ result.add(apple); } } return result; } static class Apple{ private int weight=0; private String color=""; Apple(int i,String color){ weight=i; this.color=color; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override public String toString() { return "Apple{" + "weight=" + weight + ", color='" + color + '\'' + '}'; } } }
以前比较繁琐的方式:
public class FilteringApples { public static void main(String[] args) { List<Apple> 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<Apple> list1 = FilteringApples.filter(inventory, new ColorApplePredicate()); System.out.println(list1); //[Apple{weight=155, color='green'}] List<Apple> list2 = FilteringApples.filter(inventory, new WeightApplePredicate()); System.out.println(list2); //[Apple{weight=155, color='green'}, Apple{weight=120, color='red'}] //使用匿名类 List<Apple> list3 = FilteringApples.filter(inventory, new ApplePredicate(){ @Override public boolean check(Apple apple) { return apple.getWeight()>150 || "red".equals(apple.getColor()); } }); System.out.println(list3); } //体现了策略模式,想根据什么筛选就传递什么实现类 public static List<Apple> filter(List<Apple> inventory ,ApplePredicate applePredicate){ List<Apple> result=new ArrayList<>(); for (Apple apple:inventory){ if(applePredicate.check(apple)){ result.add(apple); } } return result; } interface ApplePredicate{ boolean check(Apple apple); } static class ColorApplePredicate implements ApplePredicate{ @Override public boolean check(Apple apple) { return "green".equals(apple.getColor()); } } static class WeightApplePredicate implements ApplePredicate{ @Override public boolean check(Apple apple) { return apple.getWeight()>150; } } static class Apple{ private int weight=0; private String color=""; Apple(int i,String color){ weight=i; this.color=color; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override public String 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表达式,如下所示。
@FunctionalInterface public interface Predicate<T>{ boolean test(T t); } public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> results = new ArrayList<>(); for(T s: list){ if(p.test(s)){ results.add(s); } } return results; } Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
b、Consumer
java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T 的对象,没有返回(void)。需要访问类型T的对象,并对其执行某些操作,就可以使用 这个接口。
@FunctionalInterface public interface Consumer<T>{ void accept(T t); } public static <T> void forEach(List<T> list, Consumer<T> c){ 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<t, r="">接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。如果需要定义一个Lambda,将输入对象的信息映射 到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。在下面的代码中,展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个 String长度的Integer列表。
@FunctionalInterface public interface Function<T, R>{ R apply(T t); } public static <T, R> List<R> map(List<T> list, Function<T, R> f) { List<R> result = new ArrayList<>(); for(T s: list){ result.add(f.apply(s)); } return result; } // [7, 2, 6] List<Integer> 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<Apple> 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(new AppleComparator()); 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<Apple>() { public int compare(Apple a1, Apple a2){ return a1.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对象。
最终的代码读起来的意思就是:对库存进行排序,比较苹果的重量。

浙公网安备 33010602011771号