前言:在慕课网上学习 一课掌握Lambda表达式语法及应用 所记的笔记,供本人复习之用。
目录
第一章 为什么引入Lambda表达式
1.1 什么是Lambda
1.2 Model Code as Data
1.3 功能接口的设计及优化
在传统模式用匿名内部类实现,线程的创建需要很多行,其实真正有效的代码就一行。
如果用lambda表达式就会比较简洁
1.4 为什么要用Lambda表达式
第二章 Lambda表达式基础知识
2.1 函数式接口
函数式接口,就是Java类型系统中只包含一个接口方法的特殊接口,Java提供了语义化检测注解@FunctionalInterface来进行检测函数式接口的合法性。
当接口中有两个接口方法时便会报错,但可以有多个静态方法和默认方法和从Object继承过来的方法。
2.1.1 基本使用
对于一个函数式接口
匿名内部类实现:
lambda表达式实现:
2.1.2 jdk中常见的函数式接口
Jdk8提供了java.util.function包,提供了常用的函数式功能接口。
1.Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//省略...
}
例子:
2. Consumer
接收一个T类型的参数,不返回任何结果。
public interface Consumer<T> {
void accept(T t);
//省略...
}
例子:
3.Function
接收参数对象T,返回结果对象R
public interface Function<T, R> {
R apply(T t);
//省略...
}
例子:
4.Supplier
不接收参数,提供T对象的创建工厂
public interface Supplier<T> {
T get();
}
例子:
5.UnaryOperator
接收参数对象T,返回结果对象T
public interface UnaryOperator<T> extends Function<T, T> {
//省略...
}
例子:
6.BinaryOperator
接收两个T对象,返回一个T对象结果
总结:
这些是我们在常规的项目处理中经常用到的不同场景的函数式接口
2.2 Lambda语法及使用
基本语法:
2.2.1 变量的访问操作
匿名内部类:
在匿名内部类中,this代表的是匿名内部类,而不是App2这个类。
lamdba表达式:
s1是类中的全局变量,this指代的就是类App2而不是lambda语句块。
2.3 Lambda表达式运行原理
2.3.1 lambda表达类型检查
表达式类型检查:
声明函数式接口:
将MyInterface类型作为函数的参数
lambda表达式实现该参数
lambda表达式的类型检查:
当我们将(x,y)->{..}交给test(param)参数时,JVM会推导param参数是一个MyInterface类型的参数,所以当前的lambda表达式属于MyInterface类型,MyInterface接口就是lambda表达式的目标类型。
参数类型检查:
(x,y)->{..} -->MyInterface.strategy(T r,R r),strategy函数需要一个T类型和一个R类型,我们在把MyInterface当作类型传递给参数时,确定了它的T类型和R类型,确定了它的T类型为String,R类型为List,然后与lambda表达式进行推导验证,会得出(x,y)执行的就是strategy(T t,R r)这样一个方法,所以最终推导出来x是属于T类型即String,y属于R类型即List。
假如是如下情况,因为由类型推导,所以不会编译成功
总结:JVM会根据代码在运行过程中的上下文进行检测,在这里test需要一个MyInterface类型的参数,在调用test时我们传递了一个lambda表达式,MyInterface就是lambda表达式的目标类型,接下来会继续根据lambda表达式与绑定的接口进行类型参数的推导,在类型参数进行推导时,会验证lambda表达式中的参数个数与顺序是否和接口中定义的参数类型和顺序一致,一致的情况下按照参数的顺序进行确认。
2.3.2 方法重载与lambda表达式
匿名内部类实现:
lambda表达式实现:
有问题,因为有类型检查,而它不知道实现的是哪一个接口
2.3.3 lambda表达式底层构建原理
验证:
建立类:
反编译:
第三章 lamdba表达式高级拓展
3.1 方法引用
静态方法引用的使用
类型名称.方法名称() 到 类型名称::方法名称
实例方法的使用
创建对象,加双冒号引用
构造方法引用
它需要绑定函数式接口
如:
绑定后,执行对应方法便会返回一个Person对象。
注:方法名不用一样,参数返回值一样就行
3.2 Stream概述
这里的Stream既不是IO中的数据流Stream,也不是集合元素,也不是数据结构不能存储数据,这里讲的Stream是和数据算法与运算相关的,JDK8中stream流的引入是针对多个数据、数组、容器、集合等存储批量数据的容器聚合操作时复杂冗余的流程而提出的一套新的api,可以结合lambda表达式进行串行或者并行两种不同的方式完成对批量数据的增强操作。
为什么要学习stream,例如找出account大于5的账号
3.2 streamAPI
stream的处理流程:
获取到数据源,数据转换,获取结果
3.2.1 获取stream对象
3.2.2 中间操作API{ intermediate}
操作结果是一个stream,中间操作可以有一个或者多个连续的中间操作,需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行。
中间操作过程:无状态:数据处理时,不受前置中间操作的影响,主要包括map/filter/peek/parallel/sequential/unordered
有状态:数据处理时,受到前置中间操作的影响,主要包括distinct/sorted/limit/skip
3.2.3 终结操作|结束操作{Terminal}
需要注意:一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的
终结操作:非短路操作:当前的stream对象必须处理完集合中所有数据,才能得到处理结果。主要包括
短路操作:当前的stream对象在处理过程中,一旦满足某个条件,就可以得到结果。
3.3 Stream操作集合中的数据
3.3.1 其它类型->stream对象
在数据运算中,会对基本数据类型进行频繁的装箱拆箱操作,所以对于基本类型stream进行了一些基本的封装。
在这里的处理过程中,如果我们是通过基本的编码进行操作,new int时会出现装箱的操作,数据处理的时候又会出现拆箱的操作,在一套完整的算法中,装箱拆箱就会频繁出现,stream将它封装在了底层,封装了在做了中间操作中,最终只要完成一次装箱拆箱就可以了。
同样提供了一些基本的数据功能
3.3.2 stream对象->其它类型
3.3.3 stream常见API操作
map:
结合原本的值做一些操作,返回新的值
filter:
删除不符合条件的
peek:
上面的方式不大友好,迭代过程出现冗余,用peek进行下面的操作,迭代过程只发生了一次。
skip:
limit:
跳3条,做两条
distinct:
reduce:
合并数据并处理
sum是每次迭代后得到的结果,这里是每次相加后得到的结果。
3.4 Stream的执行效率问题
使用五种方法进行速度的测试,分别是stream,parallelStream,普通for循环,增强型for循环,迭代器
3.4.1 基本类型
stream:
parallelStream:
普通for循环:
增强型for循环:
迭代器:
一次执行结果:
3.4.2 复杂类型
类似上面的操作,只不过是找一个拥有最大属性的对象。
时间为:
3.5 Stream线程安全问题
在并行Stream的操作过程中,参考顶层运行原理,其实是将一个操作中的每个部分拆分成了多个子任务,通过多个线程执行的过程,也就是将一个大任务拆分成了几个小任务,通过线程进行完善的过程,最终将多个小任务进行合并。
在处理时,如果是串行stream,我们完全可以通过自定义多线程程序中的逻辑代码进行数据的同步来完成数据访问的控制,但是并行stream引发的多线程对于数据源是否存在线程安全的问题?
首先我们向list中添加1000个数
串行stream遍历并添加数据到list2中
并行stream遍历并添加数据到list3中
结果如下,当前Stream操作的集合并不是线程安全的,所以多个线程访问共享数据出现了冲突,最后得到的数据便会丢失。
查看官方文档:
当前并行的stream因为使用的collections就不是线程安全的,所以意味着在多线程操作的情况下,可能会因为多线程的处理过程导致一些数据不一致的错误,在处理的过程中,Collection提供了一些线程同步块,通过Collection集合本身提供的线程同步块,我们可以完成对于数据的同步处理,另外我们需要注意的是,如果使用Collection本身提供的线程同步块的话,会引起线程竞争的问题,如果我们想避免线程竞争的问题,聚合操作和parallel stream结合到一起能让我们得到一个在非线程安全的情况下对于数据的处理过程,要求是我们在操作的过程中,对于非线程安全集合中的数据不能进行修改(本人没咋看懂这段)。
另一个文档:
forEach:
我们可以用collect与reduct这样的线程安全终端操作,它们即使操作不线程安全的集合也不会出现问题。如: