1、函数式接口与lambda表达式
首先说一下函数式接口的概念:
一个接口中只有一个抽象方法,那么该接口会被当作函数式接口,这里有几个相关的注意点说明一下:
接口中的抽象方法如果是重写了Object类的方法的话,是不参与计数的
另外如果一个接口是函数式接口,那么无论我是否打上FunctionalInterface注解它都是函数式接口,而打上FunctionalInterface注解仅仅是用来按函数式接口的要求来检测该接口。
然后来说说lambda表达式:
lambda表达式是用来创建函数式接口实例的一种方式,当然你也可以使用匿名内部类、或者实现该接口的类来实例化对象,但lambda表达式显然更为简洁,最简单的格式如下:
(arg) -> {body}
arg代表抽象方法的参数,body代表抽象方法的实现,为了更形象的阐述,我们举个例子,比如Runnable接口,在jdk8中就是一个函数式接口,那么我们可以通过如下代码来理解:
new Thread( new Runnable(){ System.out.println("i am here"); } ).start();
这是匿名内部类的实现方式,下面我们使用lambda表达式:
new Thread( ()->{System.out.println("i am here");} ).start();
只要写好对应的实现即可。这部分我还想额外说一下,之前很长一段时间我一直认为lambda表达式是匿名内部类实现的一种语法糖,可现实并非如此,我们将抽象方法的实现改为System.out.println(this);,发现匿名内部类打印的是匿名内部类的一个实例对象,而lambda表达式中的this就是当前对象本身,所以可以说lambda表达式和匿名内部类并没有什么关系,只是实现了相同的功能而已。
2、高阶函数
在了解了函数式接口和lambda表达式之后,很显然,我们发现lambda表达式可以作为函数的一个载体进行传递,由此产生了高阶函数的概念:
如果一个函数接收一个函数作为参数,或者返回一个函数作为返回值,那么该函数就叫做高阶函数
3、方法引用
方法引用可以理解为lambda表达式的一种语法糖,是lambda表达式某些特化情况下的一种简化替换,具体来讲就是当lambda表达式的实现只有一行语句时,可以按如下规则进行替换:
类名::静态方法名 ----- 这种情况就是说这一行语句是调用了某个类的静态方法,而抽象方法的参数会传递给静态方法的参数
引用名(对象名)::实例方法名 ------ 这种情况是调用了实例的方法,参数传递同上
类名::实例方法名 ---- 这种情况代表抽象方法的参数列表的第一个参数作为实例方法的调用者,其余参数作为调用参数
类名::new ------ 这种情况代表返回类名对应的一个实例,抽象方法的参数会传递给构造方法
对于方法引用这种语法糖可能刚开始看的时候感觉不适应,而且它等于说是一种简化替换,但是为了阅读他人以及底层的源代码能够读得懂,还是有必要适应一下的。
4、接口规则变动
与之前版本不同的是对于jdk8的接口是可以有方法实现的,分别是静态方法和默认方法,静态方法和我们之前定义的类似,通过类名加点的方式即可调用,默认方法通过default关键字来定义,需要实例化对象之后才能调用,这两种方法的出现很大程度上的原因是在于增加了新特性之后仍然能够兼容老版本代码的一种解决方案,可以让我们的项目平滑的升级到jdk8。
5、内置api函数式支持
jdk8之后,特别是集合框架增加了一些基于内置函数式接口的api,例如List的sort方法:
default void sort(Comparator c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
再比如Iterable的forEach方法:
default void forEach(Consumer action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
Comparator接口在jdk8中很显然符合函数式接口的要求,我们可以通过传入一个lambda表达式到sort函数,代表排序的规则。对于forEach方法我们发现他定义在了Iterable接口当中,这代表所有实现了可迭代接口的集合框架类都会继承到这个默认方法,通过默认方法这种方式就解决了在增加新特性,同时基本又不对现有代码做大变动的问题,forEach方法接收的是一个Consumer接口,它代表接收一个参数不返回结果,源码如下:
@FunctionalInterface
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
这里是两个常用简单的例子,起一个抛砖引玉的作用。
6、stream api
这部分内容我认为算是新特性中比较核心的内容了,它支持以流的方式操纵集合数据,可以执行一些例如过滤、转换、分组、聚合等操作,底层实现也是比较复杂的,从流源构造,将集合的迭代器,或者是集合的数组放入分割迭代器中,然后经过多种中间操作,到最后的终止操作,完成运算,对于多种中间操作,其底层实现采用的是双向链表将多个运算连接起来,但是并不会执行运算,在终止操作时才会执行从后向前的嵌套调用,然后再从前向后一级级返回。如果终止操作是一个收集操作,那么又会涉及到收集器的概念,一个收集器包括中间结果容器提供者Supplier函数式接口,累加操作BiConsumer函数式接口,合并容器操作BinaryOperator函数式接口,完成器Function函数式接口以及一个描述元数据的属性集合,如果大家感兴趣的话可以读一下具体的源码,这里仅仅简单描述一下流程。
这里就先粗浅的写一些大方向上的内容,源码细节上以后有时间再逐个往出整理,今天暂时先写到这里把。
另外同时文章也在我自己的博客进行更新,欢迎大家访问^_^:http://e-pingtai.com/html/b87cdd9ea34e4c9e948f2169185a51e6.html