一、基本概念
两个新特性:
1、函数式编程简化代码复杂度(引入了Lambda表达式)
2、更高效的利用多核CPU
1、基本概念:
1、Lambda基本语法
(parameters) -> expression
对应:参数->表达式
或(请注意语句的花括号)
(parameters) -> { statements; }
对应:参数->语句
根据上述语法规则,以下哪个不是有效的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";}
。
2、函数式接口
函数式接口就是只定义一个抽象方法的接口。
下列哪些式函数式接口:
(1)public interface Adder{
int add(int a, int b);
}
(2)public interface SmartAdder extends Adder{
int add(double a, double b);
}
(3)public interface Nothing{
}
只有(1)是函数式接口,按照定义其他都不是。
3、Java 8中的抽象类和抽象接口
(1)一个类只能继承一个抽象类,但是一个类可以实现多个接口。
(2)一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。
4、多个接口都实现同样的默认方法,子类继承时的规则:
(1) 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
(2) 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,
优先选择拥有最具体实现的默认方法的接口,即如果B 继承了A ,那么B 就比A 更加具体。
(3) 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,
显式地选择使用哪一个默认方法的实现。
5、教你一步步完成普通方法到Lambda方法的改造
目标:对于一个`Apple`列表(`inventory`)按照重量进行排序
最终实现:`inventory.sort(comparing(Apple::getWeight))`
实现分析:Java 8
的 API
已经为你提供了一个List
可用的sort
方法,你不用自己去实现它。
那么最困难的部分已经搞定了!但是,如何把排序策略传递给sort
方法呢?你看,sort
方法的签名是这样的:
void sort(Comparator<? super E> c)
它需要一个Comparator
对象来比较两个Apple
!这就是在 Java
中传递策略的方式:它们必须包裹在一个对象里。
我们说sort
的行为被参数化了:传递给它的排序策略不同,其行为也会不同。
step1:
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
step2:
你在前面看到了,你可以使用匿名类来改进解决方案,
而不是实现一个`Comparator`却只实例化一次:
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
step3:
使用Lambda 表达式
你的解决方案仍然挺啰嗦的。Java 8引入了Lambda表达式,
它提供了一种轻量级语法来实现相同的目标:传递代码。
你看到了,在需要函数式接口的地方可以使用Lambda表达式。
我们回顾一下:函数式接口就是仅仅定义一个抽象方法的接口。
抽象方法的签名(称为函数描述符)描述了Lambda表达式的签名。
在这个例子里,Comparator代表了函数描述符(T, T) -> int。
因为你用的是苹果,所以它具体代表的就是(Apple, Apple) -> int。
改进后的新解决方案看上去就是这样的了:
inventory.sort((Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight()));
我们前面解释过了,Java 编译器可以根据Lambda 出现的上下文来推断
Lambda 表达式参数的类型 。那么你的解决方案就可以重写成这样:
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
你的代码还能变得更易读一点吗?Comparator 具有一个叫作comparing
的静态辅助方法,它可以接受一个Functio 来提取Comparable 键值,并
生成一个Comparator 对象。
它可以像下面这样用(注意你现在传递的Lambda 只有一个参数:Lambda
说明了如何从苹果中提取需要比较的键值):
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
现在你可以把代码再改得紧凑一点了:
import static java.util.Comparator.comparing;
inventory.sort(comparing((a) -> a.getWeight()));
step4:
使用方法引用
前面解释过,方法引用就是替代那些转发参数的Lambda表达式的语法糖。你可以用方法引用让你的代码更简洁
假设你静态导入了
import java.util.Comparator.comparing;
inventory.sort(comparing(Apple::getWeight));
二、流简介
流是`Java API`的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。
流可以并行的处理集合数据,无需编写复杂的多线程代码。
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
public enum Type { MEAT, FISH, OTHER }
}
目标:筛选出盘子中热量高于400的食物的名称,并按按照热量高低排序
方法一:用普通集合方式处理
List<Dish> lowCaloricDishes = new ArrayList<>();
//1、筛选
for(Dish d: menu){
if(d.getCalories() < 400){
lowCaloricDishes.add(d);
}
}
//2、排序
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
public int compare(Dish d1, Dish d2){
return Integer.compare(d1.getCalories(), d2.getCalories());
}
});
//3、统计
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish d: lowCaloricDishes){
lowCaloricDishesName.add(d.getName());
}
方法二:用流处理
List<String> lowCaloricDishesName = menu.stream()
.filter(d->d.getCalories()<400)// 筛选
.sorted(comparing(Dish::getCalories))//排序
.map(d->d.getName())
.collect(Collectors.toList());//统计
流的特点:
元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元
素(如`ArrayList` 与 `LinkedList`)。但流的目的在于表达计算,比如 `filter`、`sorted`和`map`。集合讲的是数据,流讲的是计算。
源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
数据处理操作——流的数据处理功能支持类似于数据库的操作,如`filter`、`map`、`reduce`、`find`、`match`、`sort`等。流操作可以顺序执行,也可并行执行。
流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。流水线的操作可以看作对数据源进行数据库式查询。
内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
流与集合的区别:
1、集合是一个内存中的数据结构,它包含数据结构中目前所有的值—— 集合中的每个元素都得先算出来才能添加到集合中。
2、流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。
流的操作:
1、中间操作:中间操作会返回另一个流,比如`filter、map、sort、distinct`等操作
2、终端操作: 终端操作会从流的流水线生成结果,比如`forEach、count、collect`
三、使用流
常用流的API介绍
1、筛选流
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
2、截断流
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
3、跳过元素
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
4、映射 对流中每一个元素应用函数
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
5、流的扁平化
例如, 给定单词列表["Hello","World"] ,你想要返回列表["H","e","l", "o","W","r","d"]
方式一:这个并不能返回正确的数据,解释看图5-5
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
方式二:使用`flatMap` 方法,把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。解释看图5-6
List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
6、查找和匹配
Stream API通过allMatch 、anyMatch 、noneMatch 、findFirst 和findAny
方法提供了查找和匹配的工具方法。
1、anyMatch
方法可以回答“流中是否有一个元素能匹配给定的谓词”。
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch方法返回一个boolean,因此是一个终端操作。
2、allMatch
方法的工作原理和anyMatch
类似,但它会看看流中的元素是否都能匹配给定的谓词。
boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);
3、noneMatch
没有任何元素与给定的谓词匹配。
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() < 1000);
4、findAny
方法将返回当前流中的任意元素。
Optional<Dish> dish =menu.stream().filter(Dish::isVegetarian).findAny();
Optional简介
Optional<T>类(java.util.Optional)是一个容器类,
代表一个值存在或不存在。在上面的代码中,findAny可能
什么元素都没找到。Java 8的库设计人员引入了Optional<T>,
这样就不用返回众所周知容易出问题的null了。
5、findFirst
查找第一个元素
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
6、规约
解决了如何把一个流中的元素组合起来,常用函数reduce
数据求和实现
方式一:
int sum = 0;
for (int x : numbers) {
sum += x;
}
方式二:
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
解释:
reduce接受两个参数:
一个初始值,这里是0;
一个BinaryOperator<T> 来将两个元素结合起来产生一个新的值,这里我们用的是lambda (a, b) -> a + b 。
求最大值和最小值
int max = numbers.stream().reduce(0, Integer::max);
归约方法的优势与并行化
相比于前面写的逐步迭代求和,使用reduce的好处在于,这里的迭代被内部迭代抽象掉了,
这让内部实现得以选择并行执行reduce操作。而迭代式求和例子要更新共享变量sum,
这不是那么容易并行化的。
如果你加入了同步,很可能会发现线程竞争抵消了并行本应带来的性能提升!这种计算的并
行化需要另一种办法:将输入分块,分块求和,最后再合并起来。但这样的话代码看起来就
完全不一样了。
使用流来对所有的元素并行求和时,你的代码几乎不用修改:stream()换成了`parallelStream()。
int sum = numbers.parallelStream().reduce(0, Integer::sum);
7、构建流
从数值、数组、序列、文件以及函数来构建流
7.1、数值构建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
7.2、数组构建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
7.3、函数构建流 Stream API提供了两个静态方法来从函数生成流:
Stream.iterate和 Stream.generate。
Stream.iterate迭代构建流:
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
解释:
iterate方法接受一个初始值(在这里是0),
还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator<t>类型)。
这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。
Stream.generate生成流:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
IntStream 的generate 方法会接受一个IntSupplier,可以这样来生成一个全是1 的无限流。
IntStream ones = IntStream.generate(() -> 1);
四、流的收集器
收集器非常有用,因为用它可以简洁而灵活地定义collect 用来生成结果集合的标准,一个普通的收集器
收集器的主要功能:
将流元素归约和汇总为一个值
元素分组
元素分区
首先导入类:import static java.util.stream.Collectors.*;
1、归约和汇总
long howManyDishes = menu.stream().count();
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories))
2、连接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
3、广义的归约汇总
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
解释它需要三个参数。
第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。
第二个参数就是你在6.2.2节中使用的函数,将菜肴转换成一个表示其所含热量的int。
第三个参数是一个BinaryOperator,将两个项目累积成一个同类型的值。这里它就是对两个int求和。
4、分组和分区groupingBy
4.1、分组
Map<Dish.Type, List<Dish>> dishesByType =menu.stream().collect(groupingBy(Dish::getType));
4.2、多级分组
Map<Dish.Type,Map<CaloricLevel,List<Dish>>> groupByTypeAndCaloriesMap=menu.stream()
.collect(
Collectors.groupingBy(
Dish::getType,
Collectors.groupingBy(dish -> {
if(dish.getCalories()<=400){
return CaloricLevel.DIET;
}else if(dish.getCalories()<=700){
return CaloricLevel.NORMAL;
}else{
return CaloricLevel.FAT;
}
})
));
5、分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数 。
分区函数返回一个布尔值,这意味着得到的分组Map 的键类型是Boolean ,于是它最多可以分
为两组——true 是一组,false 是一组。
Map<Boolean, List<Dish>> partitionedMenu =menu.stream().collect(partitioningBy(Dish::isVegetarian));
五、代码重构
Lambda表达式对面向对象的设计模式
说明:需要执行的动作都很简单,使用Lambda方法能很方便地消除僵化代码。但是,
观察者的逻辑有可能十分复杂,它们可能还持有状态,或定义了多个方法,在这些情
形下,你还是应该继续使用类的方式。
1、使用Lambda 表达式重构代码
//具体处理文件接口
public interface BufferReaderProcessor{
String process(BufferedReader br) throws IOException;
}
//公共逻辑抽离:文件的打开、资源关闭
public static String processFile(String filePath,BufferReaderProcessor processor) throws IOException {
try(BufferedReader br=new BufferedReader(new FileReader(filePath))){
return processor.process(br);
}
}
//客户端调用
String s1= processFile("data.txt",br->br.readLine());
String s2= processFile("data.txt",br->br.readLine()+br.readLine());
2、策略模式替换
//策略接口
public interface ValidationStrategy{
boolean execute(String s);
}
//策略执行
public class Validator{
private ValidationStrategy strategy;
public Validator(ValidationStrategy strategy){
this.strategy=strategy;
}
public boolean validator(String s){
return strategy.execute(s);
}
}
//客户端调用
Validator numberValidator=new Validator(s->s.matches("\\d+"));
Validator lowerCaseValidator=new Validator(s->s.matches("[a-z]+"));
numberValidator.validator("12345");
lowerCaseValidator.validator("abcdefg");
六、异步编程
在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,
提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,
提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和
组合CompletableFuture的方法。
1、基本用法
CompletableFuture实现了Future接口,因此可以和使用Future一样使用它。
1.1complete方法使用
CompletableFuture<String> completableFuture=new CompletableFuture();
Thread thread=new Thread(()->{
System.out.println("task doing...");
try{
Thread.sleep(1000);
}catch (Exception ex){
ex.printStackTrace();
}
System.out.println("task done");
completableFuture.complete("finished");//设置任务完成标识,否则@1会一直傻等下去
});
thread.start();
String result=completableFuture.get();//@1
System.out.println("执行结果:"+result);//返回finished
1.2 completeExceptionally 方法使用
CompletableFuture<String> completableFuture=new CompletableFuture();
Thread thread=new Thread(()->{
try{
Thread.sleep(1000);
throw new RuntimeException("抛异常了!");
}catch (Exception ex){
completableFuture.completeExceptionally(ex);//异常处理
}
});
thread.start();
String result=completableFuture.get();
2、静态方法
CompletableFuture 类自身提供了大量静态方法,使用这些方法能更方便地进行异步编程。
2.1、allOf和anyOf的使用
allOf要求所有任务都执行完成,最后汇总执行结果;anyOf只要执行最快的线程返回,汇总结果。
1、task1
CompletableFuture<String> completableFuture1=CompletableFuture.supplyAsync(()->{
try{
Thread.sleep(1500);
}catch (Exception ex){
ex.printStackTrace();
}
System.out.println("finished 1 ");
return "completableFutue1";
});
2、task2
CompletableFuture<String> completableFuture2=CompletableFuture.supplyAsync(()->{
try{
Thread.sleep(1000);
}catch (Exception ex){
ex.printStackTrace();
}
System.out.println("finished 2 ");
return "completableFuture2";
});
//两个任务都要完成才能结束
CompletableFuture<Void> allResult=CompletableFuture.allOf(completableFuture1,completableFuture2);
allResult.join();
//任一个任务结束就能返回
CompletableFuture<Object> anyResult=CompletableFuture.anyOf(completableFuture1,completableFuture2);
System.out.println("anyOf return :"+anyResult.get());
2.2 thenCompose
允许你对两个异步操作进行流水线, 第一个操作完成时,将其结果作为参数传递给第二个操作。
CompletableFuture<String> completedFuture1=CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello";
});
CompletableFuture<String> completableFuture2=completedFuture1
.thenCompose(result->CompletableFuture.supplyAsync(()->result+" world"));
String result=completableFuture2.get();
System.out.println("compose return:"+result);
3、ThenCombine
将两个完 全不相干的 CompletableFuture 对象的结果整合起来,
而且你也不希望等到第一个任务完全结束才开始第二项任务。
CompletableFuture<String> completableFuture1=CompletableFuture.supplyAsync(()->{
System.out.println("in completableFuture1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("finished completableFuture1");
return "Hello";
});
CompletableFuture<String> completableFuture2=completableFuture1
.thenCombine(CompletableFuture.supplyAsync(()-> {
System.out.println("in completableFuture2");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("finished completableFuture2");
return " World";
}),(result1,result2)->result1+result2);
System.out.println(completableFuture2.get());
4、thenAccept
在每个CompletableFuture 上注册一个操作,
该操作会在 CompletableFuture 完成执行后调用它。
CompletableFuture<String> completabledFuture1=CompletableFuture.supplyAsync(()->{
System.out.println("in completabledFuture1");
return "Hello";
});
completabledFuture1.thenAccept(result-> System.out.println("执行结果:"+result));
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。