Java函数式编程:一、函数式接口,lambda表达式和方法引用
Java函数式编程
什么是函数式编程
通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此我们会得到更加可靠的代码,并获得更高的效率
我们可以这样理解:面向对象编程抽象数据,函数式编程抽象行为。
通常而言,方法会根据所传递的数据产生不同的结果,但如果想让一个方法在每次调用时都有不同的表现该怎么办?你可能会想到多态,没错,多态是一种通过改变实际执行的方法所属对象,以此来改变实际执行的方法代码的方式,许多框架实现IoC本质上也是实现了一种自动化的多态。而这里我们要聊聊其他的两种方法。
如果我们直接将代码传递给方法,就可以控制其行为,Java 8以后,我们可以通过lambda表达式和方法引用这两个新的方法来实现这一点。
1、函数式接口
什么是函数式接口
什么是函数式接口?这是我们理解Lambda表达式以及方法引用的重点,这些接口是lambda表达式和方法引用的目标类型,这里我们引用一个比较容易理解的说法:函数式接口是一个只有一个抽象方法 (不包含祖先类Object中的公共方法,如hashcode()
等) 的接口。
当我们在编写接口时,这种函数式方法模式可以使用@FunctionalInterface
注解来强制实施,如果被注解的接口不符合标准那么就会在编译时报错。下面给出例子:
interface Functional{// 是函数式接口
String speak();
}
interface NoFunctional{// 不是函数式接口
String speak();
String laugh();
}
interface IsFunctional{// 是函数式接口,因为toString()是Object祖先类的公共方法,不算在内
String spaek();
String toString();
}
它们的意义是什么呢?
这里拿出一个例子:
interface Say{
void say();
}
class Speaker{
public static void speak(){
System.out.println("Hello, my friend!");
}
public static void main(String[] args){
Say say = Speaker::speak; // 这里的::表示我们引用了Speaker类的speak方法
say.say();
// 输出Hello, my friend!
}
}
很神奇是不是?我们直接将一个方法引用赋给了一个接口的对象。这里初看的话显然问题一堆,首先方法怎么能作为对象赋值,其次该类也没有实现该接口,最后,就算能说通,我们的接口和Speaker
类根本没有相同的方法啊!怎么就调用say.say()
效果等同于Speaker.speak()
呢?
重要:这是Java 8增加的一个小魔法:如果我们将一个方法引用或一个lambda表达式赋给一个函数式接口(且两个方法的返回值类型和参数类型可以匹配上,方法名并不重要),那么Java会自动调整这个赋值操作,使其能够匹配目标接口。
对于底层来说,这里是Java编译器创建一个实现了目标接口的类的实例,并将我们的方法引用或lambda表达式包裹在其中。
事实上,很容易预见,在这里如果我们直接将一个Speaker
对象赋给一个Say
接口,那么是无法做到的,因为Speaker
虽然符合Say
的模式,但却没有显式的实现Say
接口。幸运的是,Java 8的函数式接口允许我们直接把一个实例方法赋给这样的一个接口,这样语法更好,更简单。
Java为我们准备了大量的函数式接口,这样我们就可以尽量避免自己创建大量的接口,这些接口都可以在Java.util.function
包中轻松找到。
不过Java最多只准备了具有两个参数的函数式接口,但是接口又不难写,只要我们理解了函数式接口的意义和用法,自己写一个能容纳更多参数的函数式接口不过是信手拈来罢了。
// 举个例子,一个有四个泛型参数的函数式接口
@FunctionalInterface
public interface TriFunction<T, U, V, R>{
R apply(T t, U u, V v, R r);
}
补充
在Java给出的函数式接口中,我们只能看到一部分涉及基本类型的函数式接口。其余都是由泛型完成的,为什么要这么做呢?估计就是因为对于某些很常用的函数式接口,如整形输入+Double输出这样。如果我们采用泛型Function<Integer, Double>
来实现,就涉及到自动装箱和自动拆箱的性能问题,如果该方法会被大量调用,那么还是直接声明清楚其中的类型对于我们的整体性能更为有利
2、Lambda表达式
lambda表达式本质上是一个匿名方法,其中以->
为分隔符,在其前的是输入参数,在其后的是返回变量。
lambda表达式是函数式接口的其中一个"成果"——另一个是方法引用,我们可以将相同输入输出参数 (类型和数量都匹配) 的lambda表达式赋给一个函数式接口,通过调用赋值后的接口再来调用我们创建的lambda表达式。
lambda表达式的语法如下所示:
- 参数
->
,它可以读作产生(produces)- 方法体
msg -> msg + "!";
msg -> msg.upperCase();
() -> "hello world";
h -> {
System.out.println(h + "abcdefg");
return h.lowerCase();
}
有以下这些问题需要注意:
- lambda表达式如果只有一个参数,可以只写该参数不写括号,但你要知道这是一种特殊情况而不是相反
- 通常需要括号将参数包裹起来,为了一致性考虑,单个参数时也可以使用括号
- 没有参数时,必须要有括号指代输入的参数
- 存在多个参数时必须要以括号包裹,逗号分割
- 如果一句话就可以表示返回值,那么就直接写到方法体所在的位置,此时
return
关键字是不合法的 - 如果一句话无法囊括,那么就需要使用花括号将所有函数体包裹并以
return
返回结果
与内部匿名类相比,lambda表达式的可读性极佳,所以如果你需要使用这样的方法,你应该掌握lambda表达式。
注意,如果你要利用lambda表达式实现递归调用自身的话,必须要将该表达式赋值给一个静态变量或一个实例变量,否则该语句对于编译器可能过于复杂,会产生编译错误。
3、方法引用
方法引用指向的是方法,通过类名或对象名,跟::
,然后跟方法名就可以实现方法引用,注意,这种方法并不需要在方法名后面加上参数列表。
className::method; // 注意,没有括号!
我们可以通过一个接口,引用那些签名 (方法的参数类型数量以及返回类型) 一致的方法。
需要注意的是,任何方法要被引用,都需要该方法存在其绑定对象,这个对象对于静态方法而言就是其Class对象,而对于普通方法而言,则需要确保存在该类对象。
该对象要么被你创建出来并显式的赋给方法引用,类似这样
Object o = new o;
method(o::methodName);
这样我们的编译器就知道,这个方法是由我们的对象o
来运行的,非常的明确。
但是,还有一种情况就是未绑定的方法引用,即尚未关联到某个对象的普通方法。对于这种引用,必须先提供对象,才能进行使用。
interface Showable{
String show(Show1 s);
}
class Show1{
public String show(){
return "hello world";
}
}
public class test {
public static void main(String[] args){
Show1 s = new Show1();
Showable sa = Show1::show;
System.out.println(s.show());
// 关键就在这里,我们知道这里sa实际上没有实例,show()当然无法调用,但实际上这里最终会调用到对象s的show()方法
// 实际上,这里Java知道,它必须接受这个参数s,并且在s上面调用方法
// 如果方法有更多参数,只需遵循第一个参数取得是this这种模式
System.out.println(sa.show(s));
}
}
若不只一个参数,则如下
public class test {
public static void main(String[] args){
Show1 s = new Show1();
Showable sa = Show1::show;
System.out.println(s.show(1, 2));
System.out.println(sa.show(s, 1, 2));
}
}
interface Showable{
String show(Show1 s, int a, int b);
}
class Show1{
public String show(int a, int b){
return "hello world";
}
}
实际上,除了普通的方法外,我们还可以通过new
代替方法名来引用其构造器方法,这也是非常有用的,由于构造器方法实际上绑定该类,相当于静态方法,所以我们只需要通过接口直接调用即可。
Java函数式编程:一、函数式接口,lambda表达式和方法引用的更多相关文章
- Java中的函数式编程(三)lambda表达式
写在前面 lambda表达式是一个匿名函数.在Java 8中,它和函数式接口一起,共同构建了函数式编程的框架. lambda表达式乍看像是匿名内部类的一种语法糖,但实际上,它们是两种本质不同的事物 ...
- java8的新特性之lambda表达式和方法引用
1.1. Lambda表达式 通过具体的实例去体会lambda表达式对于我们代码的简化,其实我们不去深究他的底层原理和背景,仅仅从用法上去理解,关注两方面: lambda表达式是Java8的一个语法糖 ...
- JAVA8之Lambda表达式与方法引用表达式
一.Lambda表达式 基本语法: lambdaParameters->lambdaBody lambdaParameters传递参数,lambdaBody用于编写逻辑,lambda表达式会生成 ...
- java8 探讨与分析匿名内部类、lambda表达式、方法引用的底层实现
问题解决思路:查看编译生成的字节码文件 目录 测试匿名内部类的实现 小结 测试lambda表达式 小结 测试方法引用 小结 三种实现方式的总结 对于lambda表达式,为什么java8要这样做? 理论 ...
- 函数式编程--为什么会出现lambda表达式?
java一直处在发张和演化的过程中,其中有2个版本从根本上改变了代码的编写方式.第一个就是JDK5之后增加的泛型,还有一个就是现在介绍的函数式编程,lambda表达式. lambda表达式是java8 ...
- Java提升二:Lambda表达式与方法引用
1.Lambda表达式 1.1.定义 lambda表达式是对于函数式接口(只含有一个抽象方法的接口)的简洁实现方式.它与匿名内部类的作用相似,但是就使用范围而言,匿名内部类更为广泛,而lambda表达 ...
- Java 8 Lambda表达式之方法引用 ::双冒号操作符
双冒号运算符就是java中的方法引用,方法引用的格式是类名::方法名. 这里只是方法名,方法名的后面没有括号“()”.--------> 这样的式子并不代表一定会调用这个方法.这种式子一般是用作 ...
- 一文带你深入了解 Lambda 表达式和方法引用
前言 尽管目前很多公司已经使用 Java8 作为项目开发语言,但是仍然有一部分开发者只是将其设置到 pom 文件中,并未真正开始使用.而项目中如果有8新特性的写法,例如λ表达式.也只是 Idea Al ...
- Lambda表达式和方法引用
1 , 为什么用lambda表达式 将重复固定的代码写法简单化 2 ,lambda表达式的实质 对函数式接口的实现(一个接口中只有一个抽象方法的接口被称为函数式接口) package com.mo ...
随机推荐
- Spring源码 15 IOC refresh方法10
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- 除了Synchronized关键字还有什么可以保证线程安全?
除了Synchronized关键字还有什么可以保证线程安全? 日常使用Java开发时,多线程开发,一般就用Synchronized保证线程安全,防止并发出现的错误和异常,那么 除了Synchr ...
- 理想汽车 x JuiceFS:从 Hadoop 到云原生的演进与思考
理想汽车在 Hadoop 时代的技术架构 首先简单回顾下大数据技术的发展,基于我个人的理解,将大数据的发展分了4个时期: 第一个时期: 2006 年到 2008 年.2008 年左右,Hadoop 成 ...
- html js 导出excel表格
这个使用js 导出excel,可以集成其他语言,可以html,php,asp ,java 等,自己喜欢用那种语言就用哪种,使用非常方便.js是使用tableExport.js ,jquery-3.2. ...
- CSP-S 2020 T4 贪吃蛇 (双队列模拟)
题面 题解 先看数据,T<=10,用平衡树或优先队列是可以拿70分的,大体思路和正解思路是一样的,每次直接修改,然后模拟. 我们模拟的时候,主要是在过程中算出最终被吃的有选择权的蛇的最后选择时刻 ...
- LOJ2312 LUOGU-P3733「HAOI2017」八纵八横 (异或线性基、生成树、线段树分治)
八纵八横 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个 ...
- 基于开源方案构建统一的文件在线预览与office协同编辑平台的架构与实现历程
大家好,又见面了. 在构建业务系统的时候,经常会涉及到对附件的支持,继而又会引申出对附件在线预览.在线编辑.多人协同编辑等种种能力的诉求. 对于人力不是特别充裕.或者项目投入预期规划不是特别大的公司或 ...
- 大家都能看得懂的源码 - 那些关于DOM的常见Hook封装(二)
本文是深入浅出 ahooks 源码系列文章的第十五篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 本篇接着针对关于 DOM 的各个 Hook 封装进行解读. us ...
- 避免jquery多次监听事件
jQuery.event.dispatch 事件分发监听源码简单理解是将绑定的事件放入队列后进行监听,如果对一个事件多次绑定(on或者bind),事件会重复添加到队列等待jq监听,这样会导致很大资源消 ...
- 阿里云Centos7部署私人CSGO服务器
大四毕业生,论文和答辩分别以1.8%的重复率和只答不辨的态度双双过关.现在就是在家等着学校发毕业证了.顺带学学驾驶...可是我这么一个喜欢折腾的人,怎么能够让自己接受这么无聊的咸鱼时光呢?因为这个寒假 ...