当前位置:首页 > 技术分析 > 正文内容

什么是函数式接口?

ruisui883个月前 (03-11)技术分析22

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!

作者| 慕课网精英讲师 ColorfulC

本篇文章我们将学习函数式接口相关的知识,包括什么是函数式接口为什么需要函数式接口如何自定义一个函数式接口如何创建函数式接口的对象,以及一些 Java 内置的函数式接口的详细介绍等。

1. 什么是函数式接口

函数是接口(Functional Interface)的定义非常容易理解:只有一个抽象方法的接口,就是函数式接口。可以通过Lambda表达式来创建函数式接口的对象。

我们来看一个在之前我们就经常使用的Runnable接口,Runnable接口就是一个函数式接口,下面的截图为 Java 源码:

我们看到Runnable接口中只包含一个抽象的run()方法,并且在接口上标注了一个@FuncationInterface注解,此注解就是 Java 8 新增的注解,用来标识一个函数式接口。

2. 为什么需要函数式接口

学习了这么久的 Java,我们对 Java 是纯种的面向对象的编程语言这一概念,可能有了一定的感触,在 Java 中,一切皆是对象。但是随着Pythonscala等语言的兴起,函数式编程的概念得到开发者们的推崇,Java 不得不做出调整以支持更广泛的技术要求。

在面向函数编程的语言中,Lambda表达式的类型就是函数,但是在 Java 中,Lambda表达式的类型是对象而不是函数,他们必须依赖于一种特别的对象类型——函数式接口。所以说,Java 中的Lambda表达式就是一个函数式接口的对象。我们之前使用匿名实现类表示的对象,都可以使用Lambda表达式来表示。

3. 自定义函数式接口

想要自定义一个函数式接口也非常简单,在接口上做两件事即可:

  1. 定义一个抽象方法:注意,接口中只能有一个抽象方法;
  2. 在接口上标记@FunctionalInterface注解:当然也可以不标记,但是如果错写了多个方法,编辑器就不能自动检测你定义的函数式接口是否有问题了,所以建议还是写上吧。
/**
 * 自定义函数式接口
 * @author colorful@TaleLin
 */
@FunctionalInterface
public interface FunctionalInterfaceDemo {

    void run();

}

代码块1234567891011

由于标记了@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();
    }

}
代码块123456789101112131415161718

运行结果:

匿名内部类方式创建函数式接口
代码块1

现在,我们学习了Lambda表达式,也可以使用Lambda表达式来创建,这种方法相较匿名内部类更加简洁,也更推荐这种做法。实例代码如下:

/**
 * 测试创建函数式接口对象
 * @author colorful@TaleLin
 */
public class Test {

    public static void main(String[] args) {
        // 使用 Lambda 表达式方式创建函数式接口
        FunctionalInterfaceDemo functionalInterfaceDemo = () -> System.out.println("Lambda 表达式方式创建函数式接口");
        functionalInterfaceDemo.run();
    }

}
代码块12345678910111213

运行结果:

Lambda 表达式方式创建函数式接口
代码块1

当然,还有一种更笨的方法,写一个接口的实现类,通过实例化实现类来创建对象。由于比较简单,而且不符合我们学习函数式接口的初衷,这里就不再做实例演示了。

5. 内置的函数式接口介绍

通过上面一系列介绍和演示,相信对于函数式接口的概念和使用,你已经烂熟于心了。但是只知道这些还不够用,下面的内容才是本小节的重点,Java 中内置了丰富的函数式接口,位于java.util.function包下,学习这些函数式接口有助于我们理解 Java 函数式接口的真正用途和意义。

Java 内置了 4 个核心函数式接口:

  1. Comsumer消费型接口: 表示接受单个输入参数但不返回结果的操作,包含方法:void accept(T t),可以理解为消费者,只消费(接收单个参数)、不返回(返回为 void);
  2. Supplier供给型接口:表示结果的供给者,包含方法T get(),可以理解为供给者,只提供(返回T类型对象)、不消费(不接受参数);
  3. Function函数型接口:表示接受一个T类型参数并返回R类型结果的对象,包含方法R apply(T t)
  4. Predicate断言型接口:确定T类型的对象是否满足约束,并返回boolean值,包含方法boolean test(T t)

我们在 Java 的 api 文档中可以看到有一些方法的形参,会出现上面几类接口,我们在实例化这些接口的时候,就可以使用Lambda表达式的方式来实例化。

我们下面看几个实例,消费型接口使用实例:

实例演示

import java.util.function.Consumer;

/**
 * Java 内置4大核心h函数式接口 —— 消费型接口
 * Consumer void accept(T t)
 * @author colorful@TaleLin
 */
public class FunctionalInterfaceDemo1 {

    public static void main(String[] args) {
        Consumer consumer = s -> System.out.println(s);
        consumer.accept("只消费,不返回");
    }

}
123456789101112131415

运行结果:

只消费,不返回
代码块1

供给型接口使用实例:

实例演示

import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Java 内置4大核心h函数式接口 —— 供给型接口
 * Supplier T get()
 * @author colorful@TaleLin
 */
public class FunctionalInterfaceDemo2 {

    public static void main(String[] args) {
        Supplier supplier = () -> "只返回,不消费";
        String s = supplier.get();
        System.out.println(s);
    }

}
1234567891011121314151617

运行结果:

只返回,不消费
代码块1

下面我们使用断言型接口,来实现一个根据给定的规则,来过滤字符串列表的方法,实例如下:

实例演示

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * Java 内置4大核心函数式接口 —— 断言型接口
 * Predicate boolean test(T t)
 * @author colorful@TaleLin
 */
public class FunctionalInterfaceDemo3 {

    /**
     * 根据 Predicate 断言的结果,过滤 list 中的字符串
     * @param list 待过滤字符串
     * @param predicate 提供规则的接口实例
     * @return 过滤后的列表
     */
    public static List filterStringList(List list, Predicate predicate) {
        // 过滤后的字符串列表
        ArrayList arrayList = new ArrayList<>();
        for (String string: list) {
            if (predicate.test(string)) {
                // 如果 test 是 true,则将元素加入到过滤后的列表中
                arrayList.add(string);
            }
        }
        return arrayList;
    }

    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList<>();
        arrayList.add("Java");
        arrayList.add("PHP");
        arrayList.add("Python");
        arrayList.add("JavaScript");
        System.out.println("过滤前:");
        System.out.println(arrayList);

        List filterResult = filterStringList(arrayList, new Predicate() {
            @Override
            public boolean test(String s) {
                // 返回字符串中是否包含 P
                return s.contains("P");
            }
        });
        System.out.println("过滤后:");
        System.out.println(filterResult);
    }

}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950

运行结果:

过滤前:
[Java, PHP, Python, JavaScript]
过滤后:
[PHP, Python]
代码块1234

当然,我们学习了Lambda表达式,在main()方法中就可以不再使用匿名内部类了,改写main()方法中调用filterStringList()方法的代码:

List filterResult = filterStringList(arrayList, s -> s.contains("P"));
代码块1

上面的实例代码可能有些难以理解,跟着我的节奏来解读一下:

  • 先定义一个方法List filterStringList(List list, Predicate predicate),此方法用于根据指定的规则过滤字符串列表,接收的第一个参数为待过滤列表,第二个参数是一个函数式接口类型的规则,注意,这个参数就是规则的制定者;
  • 再看filterStringList()方法的方法体,方法体内部对待过滤列表进行了遍历,会调用Predicate接口下的boolean test(T t)方法,判断每一个字符串是否符合规则,符合规则就追加到新的列表中,最终返回一个新的过滤后的列表;
  • main()方法中,我们调用了上面定义的filterStringList()方法,第一个参数就是待过滤列表,这里的第二个参数,是我们创建的一个断言型接口的对象,其重写的test(String s)方法就是过滤规则关键所在,方法体就是判断s字符串是否包含P字符,并一个 boolean 类型的结果;
  • 理解了第二个参数通过匿名内部类创建对象的方式,再改写成通过Lambda表达式的方式创建对象,就不难理解了。

上面我们介绍了核心的内置函数式接口,理解了这些接口的使用,其他接口就不难理解了。可翻阅官方文档来查看更多。

6. 小结

通过本篇文章学习,我们知道了函数式接口就是只有一个抽象方法的接口,要使用Lambda表达式,就必须依赖函数式接口;自定义函数接口建议使用@FunctionalInterface注解来进行标注,当然如果通过 Java 内置的函数式接口就可以满足我们的需求,就不需要我们自己自定义函数式接口了。文章最后,我们通过一个较为复杂的函数式接口实例,实现了一个过滤字符串列表的方法,如果还是不能完全理解,建议同学下面多加练习。

欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/2708.html

标签: ts void
分享给朋友:

“什么是函数式接口?” 的相关文章

Slackware 15.0?发布:历史最久且在维护的Linux发行版本

Slackware 14.0 于 2012 年发布,在经过了数年的等待之后 Slackware 15.0 发行版本于今天正式发布。Slackware 于 1993 年发布,是目前历史最悠久、且仍在维护的 Linux 发行版本。Slackware 15.0 在去年进入测试阶段,在发布几个候选版本之后终...

vue组件间的九种通信方式

前言Vue组件实例间的作用域是相互独立的,而通常一个页面是由很多个组件构成,这些组件可能又嵌套了组件,形成了一个关系网图,它们的关系可能是像下图中一样,大致分为两种使用场景,父子组件间通信和非父子组件间通信,父子组件间通信又分为直接父子关系和间接父子关系。vue提供了多种通信方法,针对不同的通信需求...

带你五步学会Vue SSR

作者:liuxuan 前端名狮转发链接:https://mp.weixin.qq.com/s/6K6GUHcLwLG4mzfaYtVMBQ前言SSR大家肯定都不陌生,通过服务端渲染,可以优化SEO抓取,提升首页加载速度等,我在学习SSR的时候,看过很多文章,有些对我有很大的启发作用,有些就只是照搬官...

一起学Vue:路由(vue-router)

前言学习vue-router就要先了解路由是什么?前端路由的实现原理?vue-router如何使用?等等这些问题,就是本篇要探讨的主要问题。vue-router是什么路由是什么?大概有两种说法:从路由的用途上来解释路由就是指随着浏览器地址栏的变化,展示给用户的页面也不相同。从路由的实现原理上来解释路...

从 Vue2.0 到 React17——React 开发入门

作者:佚名来源:前端大全前言找工作时发现有一些公司是以React作为技术栈的,而且薪资待遇都不错,为了增加生存的筹码,所以还是得去学一下React,增加一项求生技能。因为我用Vue2.0开发项目已经四年了,故用Vue2.0开发项目的思路来学习React。前端项目是由一个个页面组成的,对于Vue来说,...

基于Spring Cloud+VUE的多租户小程序商城源码「快速二开可商用」

一、系统介绍JooLun平台是一个专注微信快速二开系统研发的平台,采用Java语言开发,使用的是最新微服务前后端分离技术,目前有公众号和小程序商城两个版本,有公众号后台管理、小程序商城。基于Spring Cloud微服务+VUE实现的核心框架多租户小程序商城源码,核心框架采用SpringBoot2+...