java8 函数接口,lambda和方法引用
第四十二条, lambda 优先于匿名类(lambda表达式)
1.函数接口,带有单个抽象方法的接口
2.匿名对象,创建函数对象,会很繁琐
//匿名函数创建函数对象实例
Collections.sort(new ArrayList(), new Comparator() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(),o2.length());
}
});
3.java8中带有单个抽象方法的接口是特殊的,值得特殊对待的观念,这些接口现在被称为函数接口,java允许使用
lambda表达式创建这些就接口是实例
//lambda表达式创建函数接口
Collections.sort(new ArrayList(),(x,y)->Integer.compare(x.length(),y.length()));
4.java是通过复杂的上下文推导出x,y的类型,以及返回类型和int。删除所有lambda参数的类型吧,除非他们的存在
能够使程序更加清晰,如果编译器产生一个错误的消息,告诉你无法推导的参数类型,那么你就指定类型,使用
泛型才更好的推导
5.DoubleBinaryOperator接口,带有两个double类型的参数,返回一个double类型的结果
6.lambda表达式没有名称和文档,如果一个计算本身不是自描述得,或超出了几行,那就不要把它放到一个lambda
表达式中,一行最理想,三行最大极限,如果违背这一条,可能导致程序的可读性不好
7.lambda表达式的this指的是外外围对象而不是自身,匿名函数的this指的就是匿名实例本身
8.lambda与匿名类共享你无法可靠地通过实现序列化和反序列化的属性,因此,尽可能不要(除非迫不得已)序列化
一个lambda或者匿名类,如果需要可序列化的函数对象。就使用静态嵌套类的实例
9.千万不要给函数对象使用匿名类,除非必须创建非函数接口的类型的实例
第四十三条,方法引用优先于lambda
1.实例
//lambda
map.merge(key,(count,incr)->count+incr)
//方法引用,Integer在java8中增加了求两个数的和的静态方法sum,这里直接方法引用即可
map.merge(key,Integer::sum)
2.只要方法引用可以做的事,就没有lambda做不到的(只有一种例外,JLS 9.9-2)
3.许多方法引用都会引用静态方法,但有4种方法引用不会引用静态方法
1.有限制的实例方法引用,函数对象与被引用方法带有相同的参数;
Instant.now()::isAfter Instant than = Instant.now(); t -> than.isAfter(t);
2.无限制的实例方法引用,接收对象是在运用函数对象时,通过在该方法的声明函数前面额外添加一个参数来
指定,经常用在流管道;String::toLowerCase str->str.toLowerCase()
3.类构造器;TreeMap
4.数组构造器;int[]::new len->new int[len]
5.静态;Integer::parseInt str->Integer.parseInt(str)
4.只要方法引用更加简洁,清晰,就使用方法引用,如果方法引用并不简洁,就坚持使用lambda
第四十四条,坚持使用标准的函数接口
1.只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门在构建一个新的函数接口
1.UnaryOperator
2.BinaryOperator
3.predicate
4.Supplier
5.Consumer
6.Function
这6个接口,每一个都有3种变体,分别作用于 int long double基本数据类型的,比如IntPredicate,LongUnaryOperator等,这
些参数都不能参数化(泛型)
2.Function
LongToDoubleFunction LongToIntFunction
IntToDoubleFunction IntToLongFunction
DoubleToIntFunction DoubleToLongFunction
Function
ToDoubleFunction ToIntFunction ToLongFunction
3.接收两个参数的变体
BiPredicate
BiFunction
ToIntBiFunction
Consumer接口的两个参数的变体,一个是对象,一个是基本数据类型
ObjDoubleConsumer
4.BooleanSupplier 返回Boolean类型的Supplier
5.千万不要用带包装类型的基础函数接口代替基本函数接口
6.编写自己的函数接口的条件
1.通用,并且将受益于描述性的名称
2.具有与其关联的严格的契约
3.将受益于定制的缺省方法
6.必须始终用@FunctionInterface注解对自己编写的函数接口进行标注
7.编写方法时,不要编写在同一个参数位置使用不同的函数接口的重载,比如ExecutorService.submit()就可以接收
Runnable和Callable,但是Callable却是Runnable的子类,如果Runnable数组里面,既有Runnable和Callable,那么就可能执行
Runnable而不执行Callable的submit,防止编写这样的客户端,可以参考52条建议
第四十五条,谨慎使用Stream
1.stream代表数据元素有限或无限的顺序,stream pipeline则代表这些元素的一个多级计算
2.stream pipeline通常是lazy的,等待终止操作时才会计算,所以千万不要忘了终止操作
3.默认情况下stream pipeline是按顺序执行的,也可以并发只要在stream pipeline中调用parallel()方法,一般不建议这么做(48)
4.在没有显式类型的情况下,仔细命名lambda参数,这对于stream pipeline 的可读性至关重要
5.在stream pipeline 中使用helper方法,对于可读性而然,比在迭代化代码中使用更为重要
6.避免使用stream来处理char,因为"LWH".chars(),实际上是一个int数组
7.重构现有的代码使用stream,并并且只在必要的时候才在新代码中使用
8.只能通过代码块,而不能通过函数对象来完成的场景
1.从代码块中,可以读取或者修改范围内的任意局部变量;从lambda则只能读取final或者有效的final变量,并且不能
修改任何local变量
2.从代码块中,可以从外围方法中return,break,continue外围循环,或者抛出该方法声明的任何受检异常,而lambda则
完全无法完成这些事情
9.通过函数对象来完成的场景
1.统一转换元素的序列
2.过滤元素的序列
3.利用单个操作(比如添加,链接和计算其最小值)合并元素顺序
4.将元素的序列存放到一个集合中,比如根据某些公共属性进行分组
5.搜索满足某些条件的元素的序列
6.如果实在不确定用steam还是用迭代比较更好,那么就两种都试试,看看哪一种更好
第四十六条,优先选择Stream中无副作用的函数
1.foreach操作应该只用于报告Stream计算的结果,而不是执行计算
2.静态导入collectors的所有成员是惯例也是明智的,因为这样可以提升Stream pipeline的可读性
3.学习collectors接口stream().collect(Collector)
第四十七条,collection要优先用Stream作为返回类型
1.当使用Stream作为返回值,因为Stream没有实现Iterator,所以无法使用foreach进行遍历
2.如果可以返回集合那就使用集合
3.看例子
public static void main(String[] args){
final Iterable iterator =iterableOf(ProcessHandle.allProcesses());
for (ProcessHandle processHandle : iterator) {
System.out.println(processHandle.pid());
}
}
//Stream->Iterable
public static Iterable iterableOf(Stream stream){
// return ()->stream.iterator();
return stream::iterator;
}
//Iterable->Stream
public static Stream iterableOf(Iterable iterable){
// return ()->stream.iterator();
return StreamSupport.stream(iterable.spliterator(),false);
}
- IntStream.range().mapToObj()
第四十八条,谨慎使用stream并行
1.如果源头来自Stream.iterate,或者使用了中间操作的limit,那么并行pipeline也不可能提升性能,甚至可能更耗性能,
所以千万不要随意地并行Stream pipeline
2.在Stream上通过并行获得性能,最好通过ArrayList,HashMap,HashSet,ConcurrentHashMap实例,组数,int范围和long范围,
因为这些数据结构的共性是,都可以被精确,轻松的分成任意大小的子范围,使并行线程中的分工变得更加轻松
3.Stream类库用来执行这个任务的抽象是分割迭代器,他是由stream和iterable中spliterator方法返回的
4.基本数据类型数组,数组的本身是相邻的保存在内存中的,所以并行操作将会很快
5.并行Stream不仅可能降低性能,包括活性失败,还可能导致结果出错,以及难以预计的行为
6.程序中所有的并行Stream Pipeline都是在一个通用的fork-join池中运行的,只要一个pipeline运行异常,都会损害系统
中其他不相关部份的性能
7.在适当的条件下,给Stream Pipeline添加parallel调用,确实可以在多个处理器核的情况下实现近乎线性的倍增
8.并行一个随机数的Stream,使用SplittableRandom实例开始,它比TreadLocalRandom快