慕课网Lambda笔记
第一章 Java为什么引入 Lmabda表达式
1.1 什么是Lambda表达式
Lambda表达式也被成为箭头函数、匿名函数、闭包
Lambda表达式体现的是轻量级函数式编程思想
‘->’符号是Lambda表达式的核心符号,符号左侧是操作参数,符号右侧是操作表达式
1.2 Model Code as Data
Model Code as Data,编码及数据,尽可能轻量级的将代码封装成数据。
解决方案:接口&实现了(匿名内部类)
存在问题:语法冗余、this关键字、变量捕获、数据控制等
1.3 功能接口的设计及优化
传统模式下新线程的创建
使用jdk1.8 新特性 lmabda表达式来优化线程模式
1.4 为什么要使用 Lmabda表达式
1.它不是解决未知问题的新技术
2. 他是对现有解决方案上语义的优化。
3. 需要根据实际需求考虑性能问题。
第二章 函数式接口的概述和定义
2.1函数式接口定义
函数式接口(functuon interface),就是Java类型系统中的接口
函数式接口,是只包含一个接口方法的特殊接口
语义化检测注解:@FunctionAllnterface,用来检查函数式接口的合法性
定义一个函数式接口只需要在接口中提供一个接口方法,并在接口上添加@FunctionAllnterface注解即可,如果添加了多个饿方法,@FunctionAllnterface就会报错
2.2默认方法和静态方法
2.2.1默认接口方法的特性
以当前用户身份接口为例,创建一个实现类 返回用户的身份
对当前代码进行测试
以上代码当需求进行变动后,比如要求所有的用户验证可以同时获取用户的验证信息【是否认证成功|成功-返回用户|失败-返回null】时,我们就需要修改所有的实现类代码。这时Lambda表达式可以解决这个问题,我们可以在接口中声明一个默认方法
这样我们就可以直接通过代码来调用默认方法
2.2.2 静态接口方法的特性
以消息发送接口为例,在接口中添加一个静态方法:验证消息格式是否合法
添加一个实现类,重写接口中的方法
在format方法中,我们只是打印了一句话 消息转换。。。,并返回了msg,这时候我们可以直接调用接口的静态方法来验证消息的合法性。通过执行结果可以看出,静态方法不会对函数式接口的语义也不会产生影响,默认接口,函数式接口,静态接口可以在一个接口类中同时存在。
从Object中继承的方法不会影响函数式接口
由于java中的类都直接的或者间接的继承了Object类,所以 从Object继承的方法,无论是否是抽象的,都不会影响你函数式接口的语义。例如,在 IMessageFormat 中添加一个来自object的方法 toString(),函数式接口是不会报错的
2.2.3Lambda表达式和函数式接口的关系
Lambda 只能操作一个方法 Java 中的Lambda表达式就是一个函数式接口的实现
实现接口方法的另一种方式是使用匿名内部类,通过使用匿名内部类的方式来实现用户权限的验证操作。
执行结果:
通过观察匿名内部类和前面的实现方式,都会发现,其实和数据相关的代码只有
" return “admin”.equals(username) ? “超级管理员” : “普通会员”; " 这一行,其他的代码都是冗余代码,那么能不能对代码进行优化呢?这就要使用到JDK8中的Lambda表达式。
相比较前面的匿名内部类,Lambda表达式实现方式更为简洁
执行结果:
2.2.4 JDK 中常见的函数式接口
Java类型系统内建函数式接口
JDK8提供了java.util.function包,提供了常用的函数式功能接口
1.java.util.function.Predicate
接收参数对象T,返回一个boolean类型结果,适合需要判断的场景
执行结果:
2.java.util.function.Comsumer
接收参数T,不反回结果
执行结果:
3. java.util.function.Function<T,R>
接收一个参数对象T,返回结果对象R
执行结果:
4. java.util.function.Supplier
不接受参数,提供T对象的创建工厂
执行结果:
常见的函数式接口使用
5. java.util.function.UnaryOperator
接收参数对象T,返回结果对象T ,常用于适配器模式
执行结果:
6. java.util.function.BinaryOperator
接收两个T对象,返回一个T对象的结果(使用场景:例如对两个对象进行比较,返回较大的结果)
执行结果:
总结:
java.util.function提供了大量的函数式接口
Predicate 接收参数对象T,返回一个boolean类型结果
Comsumer 接收参数T,不反回结果
Function 接收一个参数对象T,返回结果对象R
Supplier 不接受参数,提供T对象的创建工厂
UnaryOperator 接收参数对象T,返回结果对象T
BinaryOperator 接收两个T对象,返回一个T对象的结果
2.2.5 Lmabda表达式的基本语法
>1)声明:就是 Lambda表达式绑定的接口类型
2)参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数顺序一致。
3)操作符:->
4)执行代码块:包含在一对大括号中,出现在操作符的右侧
[接口声明] = (参数) ->{执行代码}
没有返回值的Lambda表达式
如果在执行代码块中,只有一行代码,大括号是可以省略的
执行结果:
带有参数 但是没有返回值得Lambda 表达式和接口
带有参数时,要将参数写在小括号中,参数的顺序和接口中定义的顺序相同
在设置参数时,也可以不写参数的类型,JVM会自动推断出参数的类型,以下方式和上面的代码是一样的
执行结果:
带有参数,带有返回值得Lambda表达式
当花括号内只有一行代码时,可以不添加花括号,同时,也不需要添加 return 关键字,虚拟机会自动帮你返回
执行结果:
总结
1 Lambda 表达式,必须和接口进行绑定
2 Lambda 表达式的参数,可以附带0到n个参数,括号中的参数类型可以不用指定,JVM在运行时,会自动根据绑定的抽象方法进行推导
3 Lambda 表达式的返回值,如果代码快只有一行并且没有大括号,不用写大括号,单行代码会自动返回,如果添加了大括号,或者代码有多行代码,必须通过return 关键字返回结果
3.6变量访问
匿名内部类中的变量访问
lamdba表达式对于变量的访问
Lambda表达式类型检查
表达式类型检查
定义一个函数式接口 MyInterface,接收两个范型R,T,并提供一个方法 strategy 接受参数T 返回参数R
定义一个方法test,接受一个Myinterface作为参数,范型为String 和List,在方法内部我们将String 添加到List集合中
分别使用匿名内部类 和Lambda表达式的方式调用test
在Lambda表达式的写法中,并没有指明方法的参数为Myinterface,而是直接传递了参数X,y,这是由底层虚拟机来自动推导出来的。
执行结果
总结
当我们使用Lambda表达式语法的时候,jvm会获取当前方法的参数来进行推导,从而自动为我们绑定方法的参数类型.这就是Lambda表达式的类型检查
3.7方法重载和Lambda表达式
首先创建一个类App4,在App4中创建两个接口 Parame1 和Parame2,并定义outInfo(String info) 方法
然后定义重载方法lambdaMethod,参数分别是Parame1和Parame2
使用传统的匿名内部类的方式调用,在main方法中创建App4的对象,使用对象点lambdaMethdo的方式new一个Parame1或者Parame2
执行结果:
使用lambda表达式的话因为是重载方法,jvm自动推导类型的时候类型检查不通过,会报如下错误
在这种情况下只能使用匿名内部类来替代lambda表达式
3.8深入理解Lambda表达式
3.8.1Lambda表达式低层解析运行原理
创建一个类App,并创建一个用于Lambda表达式执行的函数式接口IMakeUP,提供一个方法makeUp(String msg),在main方法中创建Lambda表达式打印msg
编译后,我们可以看到生成了App.class文件和IMakeUP.class文件
通过使用 javap -p App.class 命令对class文件进行反编译得到结果
Lambda表达式在JVM低层解析成私有静态方法和匿名内部类型
通过实现接口的匿名内部类型中接口方法调用静态实现方法,完成Lambda表达式的执行
第四章 Lambda表达式在集合中的运用
4.1 方法引用
- 方法引用是结合Lambda表达式的一种语法特性,
首先创建一个测试类Test, 在里面创建一个内部类Person,添加属性 name(名字),gender(性别),age(年龄),并使用lombok创建get/set方法和构造函数
- 静态方法引用
原始方式 类型名称.方法名称() -->类型名称::方法名称
初始化一些数据,并对数据进行排序
使用匿名内部类的方式进行排序
执行结果:
Lambda表达式的实现方式
执行结果:
静态方法引用
在Person类中添加一个静态方法comperByAge
执行结果:
- 实例方法引用
创建类型对应的对象 -->对象引用::实例方法名称
在Test下创建一个新类PersonUtils,添加一个方法comerByName(),根据人员的名称的hash值来进行排序
执行结果:
- 构造方法引用
构造方法的引用需要绑定一个函数式接口
首先创造一个函数式接口
4.2 Steam概述
什么是Steam
Steam是Java为了操作数组,集合来进行复杂的聚合操作而推出的一套新的API
新创建一个测试类Test2 创建一个main方法,在main方法中初始化一个字符串集合
1.要求长度大于等于五的内容为有效账号
循环方式:
迭代器方式
使用Steam结合Lambda表达式的方式
注意:这三种方式的性能是相同的,只是精简了代码长度
4.3 SteamAPI
4.3.1Steam聚合操作
什么是聚合操作?
在常规业务处理中,针对业务的批量操作,例如,在电商项目中 ,获取指定数据的年平均消费额,获取指定店铺中最便宜的商品,获取指定店铺的当月有效订单数量等等
Steam的处理流程
获取数据源->数据转换(可以执行一次或者多次)->获取结果
4.3.2获取Steam对象
从集合,数组中获取
Collection.steam(),如上一节中的account.steam(); //从集合中获取Steam对象
Collection.parallelSteam(); //获取到一个支持并发的Steam对象;
Arrs.Stream(T t); //从数组中获取Stream对象
从缓冲流中获取
BufferReader
BufferReader.lines()->stream();
静态工厂
java.util.stream.Intstream().range()…
java.nio.file.Files.work()…
自行构建
java.util.Spliterator
更多的方式
Random.ints()
Pattern.splitAsStream()…
4.3.3 Stream操作类型
Stream的操作类型主要分为两种主要类型和一种辅助类型
中间操作API
API:intermediate中间:记录操作[无状态|有状态]
它的操作结果是一个Stream对象,中间操作可以有一个或者多个连续的中间操作.
需要注意的是,所有的中间操作,只记录操作方式,不做具体执行,直到结束操作执行时,才做数据的最终执行.中间操作就是业务的逻辑处理
中间操作的过程分为有状态和无状态的操作.
无状态: 数据处理时,不受前置中间操作的影响,就是前面的中间操作不会对当前的中间操作产生影响.
无状态API:
map
filter
peek
parallel
sequential
unordered
有状态: 数据处理时,会受到前一个中间操作的影响.
例如,前一个中间操作是一个排序操作,当前中间操作是一个截取操作,有状态下,当前操作会对排序后的结果进行截取.
有状态API:
distinct
sorted
limit
skip
结束操作(终结操作)(Terminal)
一个Stream只能有一个终结操作,一旦执行终结操作,Stream就会真实处理数据,生成对应的处理结果,并且这个结果是不可逆的…
终结操作又区分为:短路和非短路操作. 短路操作和非短路操作是根据处理结果来定义的
非短路操作:Stream对象必须处理完集合中所有的数据,才能返回处理结果
非短路操作API:
forEach
forEachOrdered
toArray
reduce
collect
min
max
count
iterator
短路操作:当前的Strame对象在处理过程中,一旦满足某个条件,就可以获取结果,并不需要处理所有的数据
短路操作API:
anyMatch
allMatch
noneMatch
findFirst
findAny等
短路操作也被称作:Short-circuiting.
什么时候使用短路操作:
例如从一个无限大的Stream中返回一个有限大的Stream
4.4 Steam操作集合中的数据
4.1.1将多个数据转换为Stream对象
多个数据转换得到Stream对象
数组转换Stream对象
列表转换Stream对象
集合操作
Map操作
4.1.2Stream 对于基本数据类型的功能性封装
针对于基本数据类型Strame在运算时会进行频繁的装箱拆箱,所以对于基本数据类型进行功能性封装
只针对于常用的 int lang double 类型 ,其他的没有
以int为例
4.1.3Stream转换得到指定的数据类型(数组,集合,字符串,map)
数组
字符串
执行结果:
列表
执行结果:
集合
>执行结果:
Map
执行结果:
在这里,Collectors.toMap方法需要传入一个Function,这个接口在处理的过程中会得到两个不同的数据 ,由于我们的stream
中只包含了一个单个数据,所以我们在处理的过程中需要将Map中的数据进行单独的处理
注意:由于stream一旦进行终结操作,那么久意味着整个过程的结束,所以上述代码无法一次性全部执行,只能在执行一个的时候,将其他代码注释掉.
4.1.4 Stream常见的API操作
准备数据
在每个数据的前面增加一个字段,梁山好汉
执行结果:
添加过滤条件,来过滤符合条件的用户
执行结果:
循环遍历
使用peek来进行多次的迭代操作
通过peek可以对数据进行多次的迭代操作,由于peek是中间操作,所以在peek的过程中不会遍历数据,只有等到执行终结操作的时候才会处理数据,所以虽然进行了三次peek,但实际上只进行了一次迭代操作.
stream对于数字运算的支持
构建数据
skip()
skip操作是一个中间操作,他是一个有状态的操作 跳过部分数据,完成数据的提取
执行结果
limit()
limit 操作是一个中间操作,他是一个有状态的操作 限制输出数据的输出数量
先跳过3个数据在限制输出两个数据
执行结果:
distinct()
distinct 操作是一个中间操作,他是一个有状态的操作 去除重复数据
执行结果:
初始化数据的时候忘了加重复元素…所以这的结果和原来的数据一样 emmmm…
sorted()
sorted 操作是一个中间操作,他是一个有状态的操作 对数据进行排序
x-y是从小到大排序, y-x是从大到小排序
执行结果
max()
max 操作是一个中间操作,他是一 个有状态的操作 获取最大值
如果改变了x-y的顺序的话得到的结果也会改编.调用max后会返回一个Optional对象,调用Optional.get()即可获取结果
执行结果:
min()
minx 操作是一个中间操作,他是一个有状态的操作 获取最小值
如果改变了x-y的顺序的话得到的结果也会改编.调用min后会返回一个Optional对象,调用Optional.get()即可获取结果
执行结果
reduce()
reduce 操作是一个中间操作,他是一个有状态的操作 合并处理数据
这里对数据做了一个累加,即前面的数据想加合并,然后在与下一个数据想加调用reduce后会返回一个Optional对象,调用Optional.get()即可获取结果,这里直接使用方法链调用了
执行结果:
5 Lambda表达式在实际生产中的应用
5.1Lambda表达式重构项目
略
5.2 Lambda和Stream的性能问题
1 通过基本数据类型:整数进行测试
创建一个Integer list 集合,添加一些随机数集合数据
性能测试
1 stream 对象
创建一个方法testStreame 接收一个list集合方法,通过stream对象获取最大值
执行时间 : 641ms
2.for循环
创建一个方法testForloop 接收一个list集合方法,通过stream对象获取最大值
执行时间:94 ms
3 parallelstream
创建一个方法testParallelstream 接收一个list集合方法,通过stream对象获取最大值
执行时间: 169ms
4 增强型for循环
创建一个方法testStrongstream 接收一个list集合方法,通过stream对象获取最大值![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503202317431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3NjEyNzU1,size_16,color_FFFFFF,t_70)
执行时间:83 ms
5 迭代器操作
创建一个方法testIterator接收一个list集合方法,通过stream对象获取最大值
执行时间: 74ms
总结:串行的Stream 的执行时间比较长,并行的Stream和增强for很接近了,\
2 通过复杂数据类型:对象进行测试
创建一个对象产品.并增加一个全属性的构造方法
我们来测试获取热度最高的产品对象
创建一个对象集合,并初始化数据
通过五种方式对获取热度最高的产品进行测试
1 Stream
执行时间 :380ms
2 parallelstream
执行时间:90ms
3 for
执行时间:110ms
4 增强for
执行时间85ms’
5迭代器
执行时间 70ms
总结:在处理对象的过程中,串行Stream 依旧是执行时间最长的,parallelstream的处理事件已经可以和迭代器媲美,比普通的for要高,最快的还是迭代器.和增强for
5.3 Stream 的线程安全问题
1 Stream并行原理
通过整数列表的并行复制来测试Stream是否存在安全问题
初始化一个整数集合,分别通过串行Stream和并行Stream来进行集合间的复制,并打印源集合和复制后的集合的长度
执行结果:
源数据长度为1000,串行的Stream的长度没变,但是并行的Stream的集合长度变小了.说明并行Stream存在线程安全问题.