一、概述
1.1 为什么学?
1.能够看懂公司代码
2.大数量下处理集合效率高(得益于并行流)
3.代码可读性高
4.消灭嵌套地狱
没有使用函数式编程,一堆嵌套,不够优雅
使用函数式编程:
1.2 函数式编程思想
二、Lambda表达式入门
Lambda 表达式在 Java 8 中添加的。
lambda 表达式是一小段代码,它接受参数并返回一个值。Lambda 表达式类似于方法,但它们不需要名称,并且可以直接在方法体中实现。
我们可以将lambda表达式看作一个匿名函数,首先必须明确lambda表达式从本质上说是一个函数,所以它具备了参数列表、函数主体、返回类型、甚至可以抛出异常;还有一点,它是匿名的,所以lambda表达式没有具体的函数名称;其格式定义如下:
(参数列表) -> 表达式/表达式
下面是lambda表达式的简单例子
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 3. 返回给定字符串的长度
(String s) -> s.length()
// 4. 包含多行表达式,需用花括号括起来,并显示添加return (x + x * y)
(int x, int y) -> {
int z = x * y;
return x + z;
}
2.0 方法引用
如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码
类 :: 静态方法
Consumer<String> c = [ (s) -> System.out.println(s); 等价于 System.out::println; ]
对象 :: 实例方法
List<String> list = Lists.newArrayList();
Consumer<String> c = [ (e) => list.add(e); <=> list::add; ]
构造器 :: new
Supplier<List<String>> s = [ () -> new ArrayList<>(); <=> ArrayList::new; ]
2.1 Lambda 练习1
我们在创建线程的时候可以使用匿名内部类的写法,可以使用Lambda表达式进行修改,修改后如下:
package com.company.Lambda;
public class MyLambda {
public static void main(String[] args) {
//匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是直接用匿名内部类创建的线程");
}
}).start();
//Lambda表达式
new Thread(()->{
System.out.println("这是用Lambda表达式创建的线程");
}).start();
}
}
2.2 Lambda 练习2
package com.company.Lambda;
import java.util.function.IntBinaryOperator;
public class emp1 {
public static void main(String[] args) {
//匿名内部类
int i = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
System.out.println(i);
//Lambda表达式
int j = calculateNum(((left, right) ->
left + right ));
System.out.println(j);
}
public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a,b);
}
}
2.3 Lambda 练习3
2.4 Lambda 练习4
2.5 Lambda 练习5
2.6 总结
从上面我们看出Lambda表达式其实就是可以看作是匿名内部类的优化,直接写匿名内部类的时候,还要写各种声明等比较麻烦,Lambda直接写所用方法的参数和方法体,更让我们关注实际功能;在不熟练掌握的时候可以先按照匿名内部类的形式进行编写,然后将其改造为Lambda表达式;
2.7 Lambda表达式省略规则
参数类型可以省略
方法体如果只有一句代码时,大括号、return和分号也可以省略
方法只有一个参数时小括号也可以省略
三、Stream
3.1 概述
Java8的Stream使用的是函数式编程,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作,可以方便的让我们对集合或数组操作
3.2 案例数据准备
Author类
Book类:
获取作者信息方法:
3.3 快速入门
3.3.1 需求
3.3.2 实现
先试用内名内部类的形式:
改为Lambda表达式:
3.4 常用操作
3.4.1 创建流
3.4.2 中间操作
filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中
例如:打印所有姓名长度大于1的作家的姓名
map
可以对流中的元素进行计算和转换
例如:打印所有作家的姓名
distinct
可以去除流中重复的操作
例如:打印所有作家的姓名,并要求其中不能有重复元素
注意:distinct方法是依赖Object的equals方法来判断是否是相同的对象,所以需要重写equals方法
sorted
可以对流中的元素进行排序
例如:对流中的元素按照年龄排序,并且要求元素不能重复
limit
可以设置流的最大长度,超出的部分将被抛弃
例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名
skip
跳过流中的前n个元素,返回剩下的元素
例如:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素,看下图更容易理解,实在不理解点击下面链接看视频
因此,当对象的某个属性只有一个子属性时,可以直接使用map,例如性别,年龄等
当对象的某个属性有多个子属性时,使用flatMap比较好,例如作家写的书,书的种类等
例一:打印所有书籍的名字,要求去重
例二:打印现有数据的所有分类,要求对分类进行去重,不能出现这种格式:哲学,爱情
3.4.3 终结操作
使用Stream流的时候,一定要使用终结操作,没有终结操作,中间操作也不会执行
forEach
对流中的元素进行遍历操作,通过传入的参数去指定对遍历到的元素进行什么具体的操作
例如:输出所有作家的名字
count
可以用来统计当前流中元素的个数
例如:打印这些作家的所出书籍的数目,注意删除重复元素
max & min
用来获取流中的最值
例如:分别获取作家所出书籍的最高分和最低分
collect
将当前流转换为一个集合,这个很常用,视频:27.终结操作之collect_哔哩哔哩_bilibili
例子:获取一个存放所有作者名字的List集合
例子:获取所有书名的Set集合
例子:获取一个map,map的key为作者名,value为List<Book>
注意:转为map的时候要指定key和value
匿名内部类:
Lambda:
查找与匹配
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
例子:判断是否有年龄在29岁以上的作家
allMatch
用来判断是否都符合匹配条件,结果为boolean类型。
如果都符合结果则为true,否则结果为false
例子:判断所有的作家是否都是成年人
noneMatch
用来判断是否都不符合匹配条件,结果为boolean类型。
如果都不符合结果则为true,否则结果为false
例子:判断作家是否都没有超过100
findAny
获取流中的任意一个元素
该方法没有办法保证获取的一定是流中的第一个元素
例子:获取任意一个大于18的作家,如果存在就输出他的名字
findFirst
获取流中的第一个元素
例子: 获取一个年龄最小的作家,并输出他的姓名
reduce归并
对流中的数据按照我们指定的计算方式计算出一个结果
reduce的作用就是把stream中的元素组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值的基础上进行计算,计算结果再和后面的元素计算
reduce两个参数重载内部计算方式如下:
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的
例子:使用reduce求所有作者的年龄
例子:使用reduce求所有作者中年龄的最大值
例子:使用reduce求所有作者中年龄的最小值
reduce一个参数重载内部计算方式
3.5 Stream的注意事项
惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
流是一次性的(一旦一个流对象经过一个终结操作后,这个流就不能再被使用)
不会影响原数据(我们在流中可以多数据做很多处理,但是正常情况下是不会影响原来集合中元素的,这往往也是我们所期望的)
3.6 并行流
串行流:
并行流:
四、Optional
4.1 概述
我们在编写代码的时候出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空判断
例如:
尤其是对象中的属性还是一个对象的情况下,这种判断会更多
而过多的判断语句会让我们的代码显得臃肿不堪
所以JDK中引入了Optional,养成使用Optional的习惯可以写出更加优雅的代码来避免空指针异常
并且很多函数式编程相关的API中也使用到了Optional,如果不会使用Optional也会对函数式编程的学习造成影响
4.2 使用
4.2.1 常见对象
Optional就好像是包装类,可以把我们的具体数据封装Optional对象内部,然后我们去使用Optional中封装好的方法去操作封装进去的数据就可以非常优雅的避免空指针异常
我们一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题
你可能会觉得还要加一行代码来封装数据比较麻烦,但是如果改造下getAuthor方法,让其返回值就是封装好的Optional的话,我们在使用时就会方便很多
而且在实际开发中我们的数据很多是从数据库总获取的,Mybatis从3.5版本可以也已经支持Optional了,我们可以直接把dao方法的返回值类型定义为Optional类型,Mybatis会自己把数据封装成Optional对象返回,封装的过程也不需要我们自己操作
如果你确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象
但是一定要注意,如果使用of的时候传入的参数必须是不为null的,不然会报空指针异常
如果一个方法的返回值类型是Optional类型,而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回,这时则可以使用Optional的静态方法empty来进行封装
Optional.empty()
4.2.2 安全消费值
4.2.3 获取值
4.2.4 安全获取值
4.2.5 过滤
4.2.6 判断
4.2.7 数据转换
五、函数式接口
5.1 概述
5.2 常见的函数式接口
5.3 常用的默认方法
但是只能用匿名内部类的方法,不能改为Lambda,可以改为: