写这篇文章的目的总结梳理一下读这本书的收获,未来用到java8某些冷门特性时进行回顾,
1、函数式编程思想
java8的核心改变是提出函数式编程的思想,书中把这称作jdk变革最大的一次,函数式编程的应用场景十分普遍,如过去我们筛选苹果,筛选绿的会写一个方法,筛选大的又要写一个方法,在函数式编程里我们只需要定义一个叫做筛选苹果的方法,把筛选条件作为参数传入;又比如我们更常见的getUserById和getUserByName这些,函数式编程只要getUser(Function
2、直观的代码风格改变
java8对开发者带来最直观的改变就是lambda和stream,这两种东西改变了编程风格,这里先提前说一句lambda并不是匿名内部类的另一种表达方式,二者编译后的字节码都不一样,lambda创建额外的类被invokedynamic代替了(jdk7引入的)。stream更不是迭代器的另一种表达方式,stream可以表示无限流,延迟计算,有出色的并发能力。
2.1、Lambda
lambda是函数式接口的具体实现,形如(类型A 参数a,类型B 参数b) -> 表达式 或 (类型A 参数a,类型B 参数b) -> {表达式;return 结果},前边的小括号里要是只有一个参数可以省略类型声明和小括号,多个参数但类型一致的话也能省略类型声明,没参数的话只写小括号。“->”前边的代表函数传入的参数,后边的代表表达式返回的结果代表函数返回的结果,如Function
由于对并发性的支持,lambda内的变量要是final或近似final的,外部的引用变量可以出现再lambda内,只要引用不变就行,内容可以变,基础类型的话出现再lambda内不能修改,比如外面有个int i=0;lambda内部不能i++。
2.2、方法引用
方法引用也是一种函数实现,形如xxx::f,比如一个对象a中的一个方法是 public T f(R r);a::f的Lambda签名就是T -> R,这就可以让你自己写出方法来,然后通过方法引用当作函数形式的参数传入其他方法。
lambda表达式中若只有一个参数,表达式里只调用了该参数的方法,方法引用可直接协作参数类型的引用,如(Apple a) -> a.getWeight() 等效于 Apple::getWeight;
若只有一个参数(或没有),表达式里的方法也以这个参数为参数,方法引用可直接用表达式里的方法,如 (String s) -> System.out.println(s) 等效于System.out::println
其他方法引用的方式不一一列举了。
2.3、新的排序表达和比较器复合
过去的排序我们常用Collections.sort传入list和匿名内部类,java8在List类里加了sort方法,直接list.sort(Comparator
2.4、函数复合
函数之间可以复合成一个新的函数,用andThen或compose连接,如f = x -> x + 1; g = x -> x * 2; f.andThen(g)相当于g(f(x)),x=1时的值时4;f.compose(g)相当于f(g(x)),x=1时的值是3。
2.5、流的概念和特性
Collection新增了stream方法,直接list.stream()获取流,还可以Stream.of(a,b,c,d)创建简单的流,Stream.empty()创建空流,Arrays.stream(T[] t)由数组创建流。
对流的操作是流的内部迭代,其具有延迟性,被使用时才真正执行分配内存,所以能表示无限的东西,如Stream.iterate(0, n -> n+2)和Stream.generate(Math::random),iterate是给个起点和规则,generate接受一个Supplier
对流的操作分中间操作和终端操作,顾名思义终端操作是在最后的,而且只有一次,中间操作可以有多个。流只能消费一次,如一个流s,执行s.findFirst();再执行s.forEach()会报IllegalStateException因为流已经关闭。
常用中间操作:filter筛选,sorted排序,limit截取,map映射,skip跳过,distinct去重,flatMap扁平化映射(对于一个Stream
常见终端操作:count总数,forEach遍历,collect收集,allMatch、anyMatch、noneMatch、findFirst、findAny这些查找匹配,sum求和,reduce归约(reduce接受一个初始值和一个运算规则做参数,初始值和流的第一个做运算,得出的结果和流的下一个做运算,得出最终结果,也可以没有初始值,代表直接第一个和第二个做运算的结果和下一个运算,返回值是一个Optional)。
不考虑并行流的情况下(其实并行流也是拆分成几个同步流),流在有多个中间操作时,执行顺序也是流水线一样,一个元素执行完第一个中间操作就会执行第二个中间操作,等第一个元素执行完终端操作后下一个元素才开始执行,并不是所有元素都执行完第一个中间操作然后所有元素再执行第二个中间操作。
2.5.1、 filter 谓词复合
filter接受一个Predicate
终端操作collect接受一个收集器Collector做参数,Collectors提供了一些方法返回一个Collector:
toList
toMap(Funtion,Function),collect之后返回一个以B为key,C为value的map,但B不能重复,否则会报重复插入的异常。
maxBy(Comparator
summingInt,sumingLong,summingDouble,
averagingInt,averagingLong,averagingDouble
summarizingInt(返回一个IntSummaryStatistics类型的结果,里面由count,sum,min,average,max属性,相应的还有long的和double的)
joining连接字符串,可接受一个String参数,用这个连接如joining(",").
reducing归约,不同于终端操作reduce,它接受三或一个参数:初始值,转换操作,归约操作或者不用转换直接传归约操作。collect(reducing)返回的和reduce一样也是一个Optional.
groupingBy(Function
partitioningBy分区,传个谓词,返回一个map,key只有true和false
收集器实现的是Collector接口,包含以下五种方法:
1、Supplier supplier();
用来创建一个空的累加器实例,不需要任何参数传入,返回一个Supplier,A就是收集后返回的结果类型,比如toList()的supplier就是:
public static Supplier> supplier(){return () -> new ArrayList();}
2、BiConsumer accumulator();
执行归约操作,比如执行到第n个元素,A是由supplier提供后执行了前n-1个元素的累加器,T是第n个元素,accumulator将第n个元素也加到结果集,比如toList()的accumulator就是:
public static BiConsumer,T> accumulator{return (list,item) -> list.add(item);}
3、Function finisher(); 最后对累加器进行转换,给出最终结果比如toList(),supplier提供的就是需要的结果类型,所以只需要传进来什么传回设呢,所以:
public static Function finisher(){return Function.identity();}
4、BinaryOperator combiner(); 提供并行时各个子部分如何合并,比如toList
public static BinaryOperator combiner(){return (list1,list2) -> {list1.addAll(list2);return list1;}}
5、Set
通过以上可以自定义收集器了。
peek是一个中间操作,接收一个Consumer函数为参数,多用来debug时展示流在中间过程后的结果。
可以在stream中间操作时调用.parallel()把流转化为并行流,也可以直接用parallStream创建并行流。
并行流内部使用了ForkJoinPoll(分支合并框架,jdk7引入),默认的线程数量是处理器的数量,可以通过ForkJoinPool.common.parallelism改变线程池大小。
过去的分支合并框架操作的对象是RecursiveTask任务,内容是将一个任务按照一定规则(规则在RecursiveTask内的方法自定义实现)分成多个任务,每个任务继续分,分到不能再分为止,这些任务差不多被平均分配到ForkJoinPoll的所有线程上,每个线程都为分配给他的任务保存一个双向链式队列,每完成一个任务,就用队列头上取出下一个任务开始执行,若某一线程空了,会通过工作窃取(work stealing)“偷”走任务去执行,提高效率。java8里有新的自动机制去拆分流,就是下面要说的 Spliterator。
Spliterator接口包含以下四种方法:
1、boolean tryAdvance(Consumer
2、Spliterator
3、long estimateSize(),估算剩下多少元素要遍历
4、int characteristics(),返回int对应相应的Spliterator特性码
stream在创建时是需要spliterator为参数的,可以看构造方法的源码,有特殊需要时,可以自定义spliterator进行分割并行。
默认方法是java8在interface里新加的机制,方法声明前带着default关键字,可以实现方法的具体逻辑,实现带有默认方法接口的类不必须重写默认方法。这些抽象类和接口更相似了,java8之所以提出这么个东西是因为改的太多了,改了接口之后之前的类库要全部一起改,为了兼容老类库,就弄出了默认方法这东西。
Optional
对Future,Callable的升级,工厂方法创建CompletableFuture对象:CompletableFuture.supplyAsync(Supplier
java8的时间api提高线程安全,想解决Date和Calendar各自缺陷,新佳乐LocalDate,LocalTime,Instant,Duration,Period
java8之前不允许重复注解,例如
@Author(name="roc") @Author(name="qiang")
class Book{}
java8里只要在注解里加@Repeatable注解,在提供一个数组作为多注解容器:
@Repeatable(Authors.class)
@interface Author {String name();}
@interface Authors{
Author[] value();
}
那么上例中的多注解就可以支持,编译时会被认定为
@Authors({@Author(name="roc"),@Author(name="qiang")})
从java8开始可以对任何类型进行注解,对象也行,比如@NonNull String hello = “hello”;限定了hello不能为null
java8改变了程序设计时的思维方式,加入函数式编程的设计概念,代码上更简洁,并发处理更出色,熟悉java8的编程风格后能明显感觉到如果用java8重构之前的代码会更加简洁同时效率也会提高。