java8学习之深入函数式接口与方法引用
2017-12-28 15:18
716 查看
[b]函数式接口:[/b]
函数式接口【FunctionalInterface】是整个Lambda表达式的一个根源,换句话来说java8中的Lambda表达式要想彻底掌握,前提是要彻底理解好函数式接口,所以这次继续对函数式接口进行巩固。
先回顾一下上一次通过读FunctionalInterface这个注解的javadoc之后的三点总结【参考:http://www.cnblogs.com/webor2006/p/8111585.html】:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/adb849346459876833cb2ec6081174a2.png)
关于FunctionalInterface的doc上有一个细节还需要注意,在上次中也已经提到过,这里再拧出来看一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/570be91ecca5a4105ad00d7c4e8a3a2c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/5a86f7ec45300dc00a09feb8856e1160.png)
那换成代码如何来理解上面这段话呢?新建一个接口,里面声明一个方法,当然它是抽象的【抽象的概念是只有声明木有具体实现的】:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4a3a0e3e0eb4ec5f7afcdc1b64549b10.png)
那这个是不是FuncationalInterface呢?加上注解就可以论证了:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4528ba5812e54e0e09670fdd80740e64.png)
那如果再增加一个抽象方法:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/fd27f8b59230619967e3905489d47c21.png)
看下报错提示:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/f2fac6e390199f920cc4de8289b39cd1.png)
那如果此时将这个新加的方法名称换一个是Object类中的呢?
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4707412caef4423eedc0aec6a7039c76.png)
那为什么呢?原因就如javadoc上面的这点所描述:toString()是一个抽象方法,但是Object中也有此方法,细心的可以发现其实开发工具比较智能的在该方法的左侧已经显示出来一个箭头,点击则可以查看它父类的方法:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4101acf7474bc3fe22c5da653c8622ac.png)
那点开看一下呗:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/fb008f4905c14412127f0090dd28fb17.png)
很显然该方法是复写的Object类的中方法,所以java编译器不认为该方法是一个抽象方法,所以当然整个接口还是满足只有一个抽象方法的条件,当然认为此时的接口还是一个函数式接口啦。这是表现上的理论,那为啥要有这样的一个规定呢?其实也比较好理解:如果一个类实现该接口,那很明显该类一定有这两个方法的实现,然而java.lang.Object是所有类的父类,也就是说明具体类都会直接或间接的继承Object类中的方法,而toString()并非是子类特有的方法,所以说如果一个方法中声明的刚好是Object类中的方法,那它不算抽象方法。
接下来继续用代码来进行延深:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/a09210808b26a217a6921b2863451b5c.png)
由于MyInterface是函数式接口,所以可以改用Lambda表达式,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/9618efc91b2fb4e777cb191e53c4c1aa.png)
其实上面标红的Lamdba表达式的写法就是MyInterface的匿名实现类,所以程序可以这样写:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/c64ca3dc26ffed878b5a17202fb41983.png)
那咱们可以打印一下这个类和它父类名字,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/e7de67682b4cfebf5dfb7b8361911369.png)
那这个myInterface类的具体实现的接口是哪些呢?接着可以打印一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/830243f74cf3a34f194ac0ef75bc9ba9.png)
那这接口是谁呢?继续打印:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/2b347bd400f76ff9af4afbf5971dff03.png)
通过上面的例子对于函数式接口应该有一个比较好的认识了,所以对于它的探讨先暂时到这,接下来对于之前的例子进行一个进一步的探讨,回顾下当时的代码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/a7d606a7ac67d4cd1087f994000b84e0.png)
通过三种方式来对一个集合进行遍历,这里将重点观注在最后一种用函数式接口的方式,那这个forEach方法是来自于List类中么?点击查看下源码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/1df6add19968d927c2a60c0f0bac60f7.png)
来自于Iterable接口当中,可以看到该方法是从Java1.8才开始引入的,但是Iterable是从1.5就开始引入的:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/a69fedb551b26cb7b74b897ab934bd28.png)
而我们知道List最终是实现了Iterable这个接口,所以当然也就继承有forEach这个方法啦,这就解释了为啥可以通过List去直接调用forEach来达到遍历的目的。
这里需要注意一下细节,这个forEach方法的具体实现实际上就是写在Iterable接口当中的,但是在接口的声明前面有个default关键字,这个也在之前说了,在Java8以后在接口中可以有具体实现了,但凡在接口中有具体实现方法,前面必须加default的关键字,这称之为默认方法(Default Method),而对于实现这个接口的类也自然而然继承有这个默认方法了,有点像抽象类的概念:类中既可有抽象方法,也可以有具体方法,而继承类也会继承抽象类的具体方法。
接着来查看一下forEach javadoc的注释:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/5eb8abc8d2ca01e19237db9497f1b67d.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/0c32ceb76a143e29e7b16b0144dcdba2.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/2dcc55c239a9f622f50f96cf8352e549.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/79d608ee8052e8055f352ab674b623b7.png)
接下来再来看一下这个指定的动作Consumer,从字面意思来理解当然就是消费者的意思啦,点击看一下它的源码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/e8edb7d0f4340a69f3732338c7bd4382.png)
读一下接口的doc:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/ee25c7b051416164ea48f0f2f043fea8.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/5728e82905bc3df586ccd9aeaec8d5ce.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/c14f94e5fd4ee3f5394dd899f55d5bf2.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/c076f39061be0bca16688744094f11b0.png)
再回到咱们的程序来说,很显然可以换成Lambda表达式来改造,所有函数式接口都可以采用Lambda表达式来编写,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/ed14f7a0c4b6046e1e89c9737d4f8bae.png)
下面再对Lambda表达式进行一个总结。
[b]Lambda表达式作用:[/b]
Lambda表达式为Java添加了缺失的函数式编程特性,使得我们能将函数当做一等公民对待。
因为Java在以前方法永远都是依附于类而存在的,不可以独立存在的, 现在我们可以将方法当作参数进行传递了, 所以函数在Java8里面就成了一等公民。
在将函数作为一等公民的语言中,Lambda表达式的类型是函数,但在Java中,Lambda表达式是对象,他们必须依咐于一类特别的对象类型---函数式接口(Functional Interface)
标红的说Lambda表达式是对象,为什么呢?
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/434677994d2773da2910f74499d1e7de.png)
[b]迭代方式:[/b]
外部迭代:
什么是外部迭代呢?看程序:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/149a03875c9a7a6dc44dae76a165510f.png)
下面用图来更形象的理解:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/b08c4e503d3076d68824d01264f4819f.png)
然后一个个元素进行迭代,最后指向一个空的元素既迭代完成了,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/50d625ca13f1ce7ee5bdd7c337070f87.png)
内部迭代:
何为内部迭代,直接看代码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/dd1e9eb4b9c17f7a82ce14626206d7af.png)
之所以叫内部迭代,相对于外部迭代,当然是没有了外部迭代的迭代器啦,不借助于外部力量既完成元素的迭代。
[b]方法引用:[/b]
对于上面元素迭待的方式已经改用Lambda表达式去写,代码已经很精简了,但是!!还可以更加精简,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/23ebfe1df862933fcff275a5152fd130.png)
对于上面这种写法就叫做方法引用(method references),而这个forEach方法参数是函数式接口的实例,那意思是这个方法引用能创建函数式接口的实例?是的,在查看函数式注解的javadoc上就已经清楚的说明了,这里再来回顾一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/eb3e7a2e9565e1f62b1459ae9163027d.png)
这里看一个IDE比较智能的地方,就是在方法引用语句中的"::"处点击ctrl键之后会看到:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/d921978ccc81419410449f3324af5d5e.gif)
自动就跳到了Consumer这个函数式接口了,说明编译器识别到了这种定法就是对Consumer接口的实现,关于方法引用在之后还会仔细学习,这里有个感性的认识就行。
函数式接口【FunctionalInterface】是整个Lambda表达式的一个根源,换句话来说java8中的Lambda表达式要想彻底掌握,前提是要彻底理解好函数式接口,所以这次继续对函数式接口进行巩固。
先回顾一下上一次通过读FunctionalInterface这个注解的javadoc之后的三点总结【参考:http://www.cnblogs.com/webor2006/p/8111585.html】:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/adb849346459876833cb2ec6081174a2.png)
关于FunctionalInterface的doc上有一个细节还需要注意,在上次中也已经提到过,这里再拧出来看一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/570be91ecca5a4105ad00d7c4e8a3a2c.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/5a86f7ec45300dc00a09feb8856e1160.png)
那换成代码如何来理解上面这段话呢?新建一个接口,里面声明一个方法,当然它是抽象的【抽象的概念是只有声明木有具体实现的】:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4a3a0e3e0eb4ec5f7afcdc1b64549b10.png)
那这个是不是FuncationalInterface呢?加上注解就可以论证了:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4528ba5812e54e0e09670fdd80740e64.png)
那如果再增加一个抽象方法:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/fd27f8b59230619967e3905489d47c21.png)
看下报错提示:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/f2fac6e390199f920cc4de8289b39cd1.png)
那如果此时将这个新加的方法名称换一个是Object类中的呢?
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4707412caef4423eedc0aec6a7039c76.png)
那为什么呢?原因就如javadoc上面的这点所描述:toString()是一个抽象方法,但是Object中也有此方法,细心的可以发现其实开发工具比较智能的在该方法的左侧已经显示出来一个箭头,点击则可以查看它父类的方法:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/4101acf7474bc3fe22c5da653c8622ac.png)
那点开看一下呗:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/fb008f4905c14412127f0090dd28fb17.png)
很显然该方法是复写的Object类的中方法,所以java编译器不认为该方法是一个抽象方法,所以当然整个接口还是满足只有一个抽象方法的条件,当然认为此时的接口还是一个函数式接口啦。这是表现上的理论,那为啥要有这样的一个规定呢?其实也比较好理解:如果一个类实现该接口,那很明显该类一定有这两个方法的实现,然而java.lang.Object是所有类的父类,也就是说明具体类都会直接或间接的继承Object类中的方法,而toString()并非是子类特有的方法,所以说如果一个方法中声明的刚好是Object类中的方法,那它不算抽象方法。
接下来继续用代码来进行延深:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/a09210808b26a217a6921b2863451b5c.png)
由于MyInterface是函数式接口,所以可以改用Lambda表达式,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/9618efc91b2fb4e777cb191e53c4c1aa.png)
其实上面标红的Lamdba表达式的写法就是MyInterface的匿名实现类,所以程序可以这样写:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/c64ca3dc26ffed878b5a17202fb41983.png)
那咱们可以打印一下这个类和它父类名字,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/e7de67682b4cfebf5dfb7b8361911369.png)
那这个myInterface类的具体实现的接口是哪些呢?接着可以打印一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/830243f74cf3a34f194ac0ef75bc9ba9.png)
那这接口是谁呢?继续打印:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/2b347bd400f76ff9af4afbf5971dff03.png)
通过上面的例子对于函数式接口应该有一个比较好的认识了,所以对于它的探讨先暂时到这,接下来对于之前的例子进行一个进一步的探讨,回顾下当时的代码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/a7d606a7ac67d4cd1087f994000b84e0.png)
通过三种方式来对一个集合进行遍历,这里将重点观注在最后一种用函数式接口的方式,那这个forEach方法是来自于List类中么?点击查看下源码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/1df6add19968d927c2a60c0f0bac60f7.png)
来自于Iterable接口当中,可以看到该方法是从Java1.8才开始引入的,但是Iterable是从1.5就开始引入的:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/a69fedb551b26cb7b74b897ab934bd28.png)
而我们知道List最终是实现了Iterable这个接口,所以当然也就继承有forEach这个方法啦,这就解释了为啥可以通过List去直接调用forEach来达到遍历的目的。
这里需要注意一下细节,这个forEach方法的具体实现实际上就是写在Iterable接口当中的,但是在接口的声明前面有个default关键字,这个也在之前说了,在Java8以后在接口中可以有具体实现了,但凡在接口中有具体实现方法,前面必须加default的关键字,这称之为默认方法(Default Method),而对于实现这个接口的类也自然而然继承有这个默认方法了,有点像抽象类的概念:类中既可有抽象方法,也可以有具体方法,而继承类也会继承抽象类的具体方法。
接着来查看一下forEach javadoc的注释:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/5eb8abc8d2ca01e19237db9497f1b67d.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/0c32ceb76a143e29e7b16b0144dcdba2.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/2dcc55c239a9f622f50f96cf8352e549.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/79d608ee8052e8055f352ab674b623b7.png)
接下来再来看一下这个指定的动作Consumer,从字面意思来理解当然就是消费者的意思啦,点击看一下它的源码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/e8edb7d0f4340a69f3732338c7bd4382.png)
读一下接口的doc:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/ee25c7b051416164ea48f0f2f043fea8.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/5728e82905bc3df586ccd9aeaec8d5ce.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/c14f94e5fd4ee3f5394dd899f55d5bf2.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/c076f39061be0bca16688744094f11b0.png)
再回到咱们的程序来说,很显然可以换成Lambda表达式来改造,所有函数式接口都可以采用Lambda表达式来编写,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/ed14f7a0c4b6046e1e89c9737d4f8bae.png)
下面再对Lambda表达式进行一个总结。
[b]Lambda表达式作用:[/b]
Lambda表达式为Java添加了缺失的函数式编程特性,使得我们能将函数当做一等公民对待。
因为Java在以前方法永远都是依附于类而存在的,不可以独立存在的, 现在我们可以将方法当作参数进行传递了, 所以函数在Java8里面就成了一等公民。
在将函数作为一等公民的语言中,Lambda表达式的类型是函数,但在Java中,Lambda表达式是对象,他们必须依咐于一类特别的对象类型---函数式接口(Functional Interface)
标红的说Lambda表达式是对象,为什么呢?
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/434677994d2773da2910f74499d1e7de.png)
[b]迭代方式:[/b]
外部迭代:
什么是外部迭代呢?看程序:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/149a03875c9a7a6dc44dae76a165510f.png)
下面用图来更形象的理解:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/b08c4e503d3076d68824d01264f4819f.png)
然后一个个元素进行迭代,最后指向一个空的元素既迭代完成了,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/50d625ca13f1ce7ee5bdd7c337070f87.png)
内部迭代:
何为内部迭代,直接看代码:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/dd1e9eb4b9c17f7a82ce14626206d7af.png)
之所以叫内部迭代,相对于外部迭代,当然是没有了外部迭代的迭代器啦,不借助于外部力量既完成元素的迭代。
[b]方法引用:[/b]
对于上面元素迭待的方式已经改用Lambda表达式去写,代码已经很精简了,但是!!还可以更加精简,如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/23ebfe1df862933fcff275a5152fd130.png)
对于上面这种写法就叫做方法引用(method references),而这个forEach方法参数是函数式接口的实例,那意思是这个方法引用能创建函数式接口的实例?是的,在查看函数式注解的javadoc上就已经清楚的说明了,这里再来回顾一下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/eb3e7a2e9565e1f62b1459ae9163027d.png)
这里看一个IDE比较智能的地方,就是在方法引用语句中的"::"处点击ctrl键之后会看到:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/12/d921978ccc81419410449f3324af5d5e.gif)
自动就跳到了Consumer这个函数式接口了,说明编译器识别到了这种定法就是对Consumer接口的实现,关于方法引用在之后还会仔细学习,这里有个感性的认识就行。
相关文章推荐
- Java8学习笔记(二)-函数式接口与方法引用
- Java8 lambda表达式、函数式接口、方法引用
- [java8] lambda表达式、函数式接口和方法引用
- Java8特性总结(二)Lambda表达式,函数式接口,方法引用
- 必看:深入学习Java8中的函数式接口
- java8新特性之函数式接口、lambda表达式、接口的默认方法、方法和构造函数的引用
- Java 8 函数式接口、lambda表达式、方法以及构造器引用
- Java学习之数组1(1.数组的声明;2.元素为引用数据类型的数组;3.关于main方法里的String[] args;4.数组排序;5.数3退1 数组算法,(用数组模拟链表);6数组查找之二分法;7数组的拷贝)
- Java学习笔记之深入理解引用
- 方法接口spring源码学习之路---深入AOP(终)
- JAVA学习--接口使用方法
- JAVA学习--接口的应用:工厂方法的设计模式
- Java8学习笔记(1) -- 从函数式接口说起
- JAVA学习笔记32——hashCode和equals方法+set接口
- JAVA学习--接口使用方法2(接口间…
- 14. JAVA 枚举(Enum、类集EnumMap&EnumSet 、实现接口、定义抽象方法) ----- 学习笔记
- java学习:类和方法的说明符、访问权限、抽象类、接口
- 在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口 (学习老罗的)
- 深入理解Java的方法调用一(值传递和引用传递)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)