函数式接口
在上个小节的最后,我们提到了函数式接口的概念,也知道了想要使用Lambda
表达式,则必须依赖函数式接口。本小节我们将学习函数式接口相关的知识,包括 什么是函数式接口 , 为什么需要函数式接口 , 如何自定义一个函数式接口 , 如何创建函数式接口的对象 ,以及一些 Java 内置的函数式接口 的详细介绍等。本小节内容较为简单,但需要读者有Lambda
表达式前置知识,学习重点是要了解 Java 内置函数式接口。
1. 什么是函数式接口
函数是接口(Functional Interface
)的定义非常容易理解:只有一个抽象方法的接口,就是函数式接口。可以通过Lambda
表达式来创建函数式接口的对象。
我们来看一个在之前我们就经常使用的Runnable
接口,Runnable
接口就是一个函数式接口,下面的截图为 Java 源码:
我们看到Runnable
接口中只包含一个抽象的run()
方法,并且在接口上标注了一个@FuncationInterface
注解,此注解就是 Java 8 新增的注解,用来标识一个函数式接口。
2. 为什么需要函数式接口
学习了这么久的 Java,我们对 Java 是纯种的面向对象的编程语言 这一概念,可能有了一定的感触,在 Java 中, 一切皆是对象 。但是随着Python
、scala
等语言的兴起,函数式编程的概念得到开发者们的推崇,Java 不得不做出调整以支持更广泛的技术要求。
在面向函数编程的语言中,Lambda
表达式的类型就是函数,但是 在 Java 中,Lambda
表达式的类型是对象而不是函数,他们必须依赖于一种特别的对象类型——函数式接口。所以说,Java 中的Lambda
表达式就是一个函数式接口的对象。我们之前使用匿名实现类表示的对象,都可以使用Lambda
表达式来表示。
3. 自定义函数式接口
想要自定义一个函数式接口也非常简单,在接口上做两件事即可:
- 定义一个抽象方法 :注意,接口中只能有一个抽象方法;
-
在接口上标记
@FunctionalInterface
注解:当然也可以不标记,但是如果错写了多个方法,编辑器就不能自动检测你定义的函数式接口是否有问题了,所以建议还是写上吧。/* * 自定义函数式接口 * @author colorful@TaleLin / @FunctionalInterface public interface FunctionalInterfaceDemo {
void run();
}
由于标记了@FunctionalInterface
注解,下面接口下包含两个抽象方法的这种错误写法,编译器就会给出提示:
4.创建函数式接口对象
在上面,我们自定义了一个函数式接口,那么如何创建它的对象实例呢?
我们可以使用匿名内部类来创建该接口的对象,实例代码如下:
/**
* 测试创建函数式接口对象
* @author colorful@TaleLin
*/
public class Test {
public static void main(String[] args) {
// 使用匿名内部类方式创建函数式接口
FunctionalInterfaceDemo functionalInterfaceDemo = new FunctionalInterfaceDemo() {
@Override
public void run() {
System.out.println("匿名内部类方式创建函数式接口");
}
};
functionalInterfaceDemo.run();
}
}
运行结果:
匿名内部类方式创建函数式接口
现在,我们学习了Lambda
表达式,也可以使用Lambda
表达式来创建,这种方法相较匿名内部类更加简洁,也更推荐这种做法。实例代码如下:
/**
* 测试创建函数式接口对象
* @author colorful@TaleLin
*/
public class Test {
public static void main(String[] args) {
// 使用 Lambda 表达式方式创建函数式接口
FunctionalInterfaceDemo functionalInterfaceDemo = () -> System.out.println("Lambda 表达式方式创建函数式接口");
functionalInterfaceDemo.run();
}
}
运行结果:
Lambda 表达式方式创建函数式接口
当然,还有一种更笨的方法,写一个接口的实现类,通过实例化实现类来创建对象。由于比较简单,而且不符合我们学习函数式接口的初衷,这里就不再做实例演示了。
5. 内置的函数式接口介绍
通过上面一系列介绍和演示,相信对于函数式接口的概念和使用,你已经烂熟于心了。但是只知道这些还不够用,下面的内容才是本小节的重点,Java 中内置了丰富的函数式接口,位于java.util.function
包下,学习这些函数式接口有助于我们理解 Java 函数式接口的真正用途和意义。
Java 内置了 4 个核心函数式接口:
Comsumer<T>
消费型接口 : 表示接受单个输入参数但不返回结果的操作,包含方法:void accept(T t)
,可以理解为消费者,只消费(接收单个参数)、不返回(返回为void
);Supplier<T>
供给型接口 :表示结果的供给者,包含方法T get()
,可以理解为供给者,只提供(返回T
类型对象)、不消费(不接受参数);Function<T, R>
函数型接口 :表示接受一个T
类型参数并返回R
类型结果的对象,包含方法R apply(T t)
;Predicate<T>
断言型接口 :确定T
类型的对象是否满足约束,并返回boolean
值,包含方法boolean test(T t)
。
我们在 Java 的 api
文档中可以看到有一些方法的形参,会出现上面几类接口,我们在实例化这些接口的时候,就可以使用Lambda
表达式的方式来实例化。
我们下面看几个实例,消费型接口使用实例:
运行结果:
只消费,不返回
供给型接口使用实例:
运行结果:
只返回,不消费
下面我们使用断言型接口,来实现一个根据给定的规则,来过滤字符串列表的方法,实例如下:
运行结果:
过滤前:
[Java, PHP, Python, JavaScript]
过滤后:
[PHP, Python]
当然,我们学习了Lambda
表达式,在main()
方法中就可以不再使用匿名内部类了,改写main()
方法中调用filterStringList()
方法的代码:
List<String> filterResult = filterStringList(arrayList, s -> s.contains("P"));
上面的实例代码可能有些难以理解,跟着我的节奏来解读一下:
- 先定义一个方法
List<String> filterStringList(List<String> list, Predicate<String> predicate)
,此方法用于根据指定的规则过滤字符串列表,接收的第一个参数为待过滤列表,第二个参数是一个函数式接口类型的规则,注意,这个参数就是规则的制定者; - 再看
filterStringList()
方法的方法体,方法体内部对待过滤列表进行了遍历,会调用Predicate<T>
接口下的boolean test(T t)
方法,判断每一个字符串是否符合规则,符合规则就追加到新的列表中,最终返回一个新的过滤后的列表; - 在
main()
方法中,我们调用了上面定义的filterStringList()
方法,第一个参数就是待过滤列表,这里的第二个参数,是我们创建的一个断言型接口的对象,其重写的test(String s)
方法就是过滤规则关键所在,方法体就是判断s
字符串是否包含P
字符,并一个 boolean 类型的结果; - 理解了第二个参数通过匿名内部类创建对象的方式,再改写成通过
Lambda
表达式的方式创建对象,就不难理解了。
上面我们介绍了核心的内置函数式接口,理解了这些接口的使用,其他接口就不难理解了。可翻阅官方文档来查看更多。
6. 小结
通过本小节的学习,我们知道了函数式接口就是只有一个抽象方法的接口,要使用Lambda
表达式,就必须依赖函数式接口;自定义函数接口建议使用@FunctionalInterface
注解来进行标注,当然如果通过 Java 内置的函数式接口就可以满足我们的需求,就不需要我们自己自定义函数式接口了。本小节的最后,我们通过一个较为复杂的函数式接口实例,实现了一个过滤字符串列表的方法,如果还是不能完全理解,建议同学下面多加练习。
访问者可将本网站提供的内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯本网站及相关权利人的合法权利。
本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站,邮箱:80764001@qq.com,予以删除。