java8 函数接口,lambda和方法引用
第四十二条, lambda 优先于匿名类(lambda表达式)
1.函数接口,带有单个抽象方法的接口
2.匿名对象,创建函数对象,会很繁琐
//匿名函数创建函数对象实例
Collections.sort(new ArrayList<String>(), new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(),o2.length());
}
});
3.java8中带有单个抽象方法的接口是特殊的,值得特殊对待的观念,这些接口现在被称为函数接口,java允许使用
lambda表达式创建这些就接口是实例
//lambda表达式创建函数接口
Collections.sort(new ArrayList<String>(),(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<K,V>::new ()->new TreeMap<K,V>
4.数组构造器;int[]::new len->new int[len]
5.静态;Integer::parseInt str->Integer.parseInt(str)
4.只要方法引用更加简洁,清晰,就使用方法引用,如果方法引用并不简洁,就坚持使用lambda
第四十四条,坚持使用标准的函数接口
1.只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门在构建一个新的函数接口
1.UnaryOperator<T> T apply(T y) String::toLowerCase
2.BinaryOperator<T> T apply(T t1,T t2) BigInteger::add
3.predicate<T> boolean test(T t) Collection::isEmpty
4.Supplier<T> T get() Instant::now
5.Consumer<T> void accept(T t) System.out::println
6.Function<T,R> R apply(T t) Arrays::asList
这6个接口,每一个都有3种变体,分别作用于 int long double基本数据类型的,比如IntPredicate,LongUnaryOperator等,这
些参数都不能参数化(泛型)
2.Function<int[]>,它的参数化类型代表的是返回值的类型,Function接口有9个变种
LongToDoubleFunction LongToIntFunction
IntToDoubleFunction IntToLongFunction
DoubleToIntFunction DoubleToLongFunction
Function<T, U>有3个变体,返回 int long double基本数据类型
ToDoubleFunction ToIntFunction ToLongFunction
3.接收两个参数的变体
BiPredicate<T, U> BiFunction<T, U, R> BiConsumer<T, U>
BiFunction<T, U, R>有3个变体,返回 int long double基本数据类型
ToIntBiFunction<T, U> ToDoubleBiFunction<T, U> ToLongBiFunction<T, U>
Consumer接口的两个参数的变体,一个是对象,一个是基本数据类型
ObjDoubleConsumer<T> ObjIntConsumer<T> ObjLongConsumer<T>
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<ProcessHandle> iterator =iterableOf(ProcessHandle.allProcesses());
for (ProcessHandle processHandle : iterator) {
System.out.println(processHandle.pid());
}
}
//Stream->Iterable
public static <E> Iterable<E> iterableOf(Stream<E> stream){
// return ()->stream.iterator();
return stream::iterator;
}
//Iterable->Stream
public static <E> Stream<E> iterableOf(Iterable<E> 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快