前面介绍了Lambda表达式的用法,从实践中发现它确实极大地方便了开发者,然而不管是匿名内部类还是Lambda表达式,所举的例子都离不开各类数组的排序方法,倘使Lambda表达式仅能用于sort方法,无疑限制了它的应用范围。那么除了sort方法,还有哪些场景能够将Lambda表达式派上用场呢?既然匿名内部类与Lambda表达式都依附于某种接口,追根究底,就得好好研究一下这种接口的特别之处。
关于排序方法sort的第二个输入参数,原本定义的参数类型是比较器Comparator,可是这个比较器真正有用的实乃唯一一个抽象方法compare。之前阐述Lambda表达式概念的时候,提到Lambda表达式指的是匿名方法,并且由于Java不支持把方法作为参数类型,因此只好再给方法加一层接口的包装,于是sort方法里的参数类型变为Comparator接口而非compare方法了。
像Comparator这种挂羊头卖狗肉的接口,表面上是接口的结构,实际上给某个方法专用,为了有别于其它普通接口,它被Java称作“函数式接口”。函数式接口拥有一般接口的形态,但其内部有且仅有一个抽象方法(方法也叫做函数),而这也是外部调用时采取Lambda表达式改写的方法。除此之外,函数式接口还允许定义别的非抽象方法,包括默认方法与静态方法。
搞清楚了函数式接口的来龙去脉,接下来不妨自定义一个全新的函数式接口。之前讲到普通接口之时,定义了一个行为接口给各个动物类实现,这意味着行为动作的方法代码与类定义代码在一起定义。如果来了一个新的动物,就得提供对应的动物类定义及其动作代码,日积月累各种动物类势必越来越多。不过很多业务场景希望更灵活的逻辑,往往只要定义一个基础的动物类,然后动物的每样属性都由成员方法读写,甚至动物的行为动作也由外部传入。这样可以制定一个“行动”方法,并通过“行为”接口包装起来,再提供给动物类使用。下面便是一个最简单的行为接口代码例子:

//定义一个行为接口,给动物类调用
public interface Behavior { // 声明一个名叫行动的抽象方法
public void act();
}

从上面的接口定义可知,Behavior接口有且仅有一个抽象方法act,因而它属于函数式接口。接着编写动物类的定义代码,其中的midnight方法用来控制该动物在半夜干什么,具体的行动内容由输入参数指定(参数类型为Behavior),具体的动物类代码如下所示:

//演示动物类的定义,其中midnight方法的输入参数为Behavior类型
public class Animal { // 定义一个名称属性
private String name; public Animal(String name) {
this.name = name;
} public String getName() {
return this.name;
} // 定义一个半夜行动的方法。具体的动作由输入行为的act方法执行
public void midnight(Behavior behavior) {
behavior.act();
}
}

然后外部就能创建动物类Animal的实例,并调用该实例的midnight方法传入规定的行为动作。以公鸡为例,大公鸡最喜欢在半夜鸡叫了,那么先创建一只公鸡实例,再命令它的midnight方法执行叫唤动作,这里的叫唤动作若以匿名内部类书写的话,可参考下列的调用代码:

	// 测试公鸡在半夜干了啥
private static void testCock() {
Animal cock = new Animal("公鸡");
// 调用midnight方法时,传入匿名内部类的实例
cock.midnight(new Behavior() {
@Override
public void act() {
System.out.println(cock.getName()+"在叫啦。");
}
});
}

把以上的匿名内部类写法改为Lambda表达式,将冗余部分掐头去尾简化成了如下一行代码:

		// 调用midnight方法时,传入Lambda表达式的代码。
// 匿名方法不存在输入参数的话,也要保留一对圆括号占位子。
cock.midnight(() -> System.out.println(cock.getName()+"在叫啦。"));

单单看这个Lambda表达式,姑且不论事实上的参数类型为何,至少在表面上是把一段方法代码作为输入参数传给了midnight。如此一来,函数式接口借助Lambda表达式,成功地瞒天过海摇身变成了一种方法类型。
继续演示其它动物,每当夜深人静的时候,老猫便瞪圆眼睛出来捉老鼠了,于是往老猫实例的midnight方法输入捉老鼠动作,相应的调用代码如下所示:

	// 测试老猫在半夜干了啥
private static void testCat() {
Animal cat = new Animal("老猫");
// 调用midnight方法时,传入Lambda表达式的代码
cat.midnight(() -> System.out.println(cat.getName()+"在捉老鼠。"));
}

可见函数式接口结合Lambda表达式,将与行为有关的代码减肥减得不能再瘦了。再奉上一段猪仔在半夜呼呼大睡的代码例子:

	// 测试猪仔在半夜干了啥
private static void testPig() {
Animal pig = new Animal("猪仔");
// 调用midnight方法时,传入Lambda表达式的代码
pig.midnight(() -> System.out.println(pig.getName()+"在呼呼大睡。"));
}

最后运行上述三种动物的测试代码,得到以下的日志结果:

公鸡在叫啦。
老猫在捉老鼠。
猪仔在呼呼大睡。

总结一下,函数式接口适用于外部把某个方法当作输入参数的场合。通过利用函数式接口,一群相似的实体支持在调用之时单独传入个体动作,而无需像从前那样派生出许多子类,还要在各个子类中分别实现它们的动作方法。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(六十二)如何定义函数式接口的更多相关文章

  1. Java开发笔记(十二)布尔变量论道与或非

    在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...

  2. Java开发笔记(一百二十六)Swing的窗口

    前面介绍了AWT界面编程的若干技术,在编码实践的时候,会发现AWT用起来甚是别扭,它的毛病包括但不限于下列几点:1.对中文的支持不好,要想在界面上正常显示汉字,还得在运行时指定额外的运行参数“-Dfi ...

  3. Java开发笔记(一百二十五)AWT图像加工

    前面介绍了如何使用画笔工具Graphics绘制各种图案,然而Graphics并不完美,它的遗憾之处包括但不限于:1.不能设置背景颜色:2.虽然提供了平移功能,却未提供旋转功能与缩放功能:3.只能在控件 ...

  4. Java开发笔记(一百二十)AWT文本标签

    前面介绍了AWT窗口及其面板的简单用法,其中展示出来的控件只有按钮一种,还有很多好用好玩的控件有待介绍.首先是文本标签Label,该控件用于显示一段平铺文本,它不花哨也不跳动,完全就是素面朝天的文本字 ...

  5. Java开发笔记(一百二十二)AWT选择框

    前面介绍了两种文本输入框的用法,不过实际应用很少需要用户亲自文字,而是在界面上列出几个选项,让用户勾勾点点完成选择,这样既方便也不容易弄错.依据选择的唯一性,可将选项控件分为两类:一类是在方框中打勾的 ...

  6. Java开发笔记(一百二十四)AWT绘图操作

    前面介绍了如何使用画笔在控件上展示图像,可是图像来源于磁盘图片,无法即兴绘制个性化的图案.所幸画笔工具Graphics不仅能够描绘图像,还支持绘制常见的几何形状,也支持绘制文本字符串,除了绘制图像用到 ...

  7. Java开发笔记(一百二十八)Swing的图标

    前面提过,AWT没提供能够直接显示图像的控件,这无疑是个令人诟病的短板,因为一上来就得由程序员自己去定义新控件,对于初学者来讲很不友好.这个问题在Swing中也解决掉了,不过Swing并未提供单独的图 ...

  8. Java开发笔记(一百二十九)Swing的输入框

    Swing的输入框仍然分成两类:单行输入框和多行输入框,但与AWT的同类控件相比,它们在若干细节上有所调整.首先说单行输入框,AWT的单行输入框名叫TextField,平时输入什么字符它便显示什么字符 ...

  9. Java开发笔记(一百二十三)AWT图像视图

    前面介绍了AWT的几种基础控件,从按钮到文本标签,从输入框到选择框,无一例外都能显示文字,唯独无法显示某张图片文件.本以为AWT会提供专门的控件来显示图片,然而偏偏没有意料之中的图像控件,这可真是弱爆 ...

  10. Java开发笔记(九十二)文件通道的基本用法

    前面介绍的各色流式IO在功能方面着实强大,处理文件的时候该具备的操作应有尽有,可流式IO在性能方面不尽如人意,它的设计原理使得实际运行效率偏低,为此从Java4开始增加了NIO技术,通过全新的架构体系 ...

随机推荐

  1. sublime No packages available for installation

    package control user-setting 中添加: "channels": [ "C:\\channel_v3.json" ], channel ...

  2. C# Winform ListView实现单元格双击复制内容到剪贴板

    private void listView_MouseDoubleClick(object sender, MouseEventArgs e) { ListView listview = (ListV ...

  3. vs2017 调试时 浏览器关闭不想中断调试

    解决方案 工具—>选项—>项目和解决方案—>web项目-->去点“浏览器窗口关闭时停止调试”前面的勾去掉>>>

  4. [Swift]LeetCode699. 掉落的方块 | Falling Squares

    On an infinite number line (x-axis), we drop given squares in the order they are given. The i-th squ ...

  5. 死磕 java集合之LinkedHashMap源码分析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问 ...

  6. Spring Boot2.0 设置拦截器

    所有功能完成 配置登录认证 配置拦截器 在spring boot2.0 之后 通过继承这个WebMvcConfigurer类 就可以完成拦截 新建包com.example.interceptor; 创 ...

  7. 【WCF系列】(三)如何配置和承载服务

    如何配置和承载服务 配置绑定 配置服务:任务 为什么要配置服务:在设计和实现服务协定后,即可配置服务. 在其中可以定义和自定义如何向客户端公开服务指定可以找到服务的地址.服务用于发送和接收消息的传输和 ...

  8. LeetCode专题-Python实现之第9题:Palindrome Number

    导航页-LeetCode专题-Python实现 相关代码已经上传到github:https://github.com/exploitht/leetcode-python 文中代码为了不动官网提供的初始 ...

  9. MarkDown语法总结

    MarkDown常用语法讲解,写博客使用. 一丶标题的设置. 标题设置使用#来区分一级标题,几个#代表几级标题. 图片如下. 二丶添加代码块,以及标记代码. 1.添加代码块使用语法三个 '设置 2.添 ...

  10. Java开发知识之Java字符串类

    Java开发知识之Java字符串类 一丶简介 任何语言中.字符串都是很重要的.都涉及到字符串的处理. 例如C++中. 字符串使用内存. 并提供相应的函数进行处理 strcmp strcat strcp ...