章节索引 :

函数式接口概述

函数式接口概述

在 Java 里面,所有的方法参数都是有固定类型的,比如将数字 9 作为参数传递给一个方法,它的类型是 int;字符串 “9” 作为参数传递给方法,它的类型是 String。那么 Lambda 表达式的类型由是什么呢?通过本节我们学习什么是函数式接口,它与 Lambda 表达式的关系。

1. 什么是函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,它可以被隐式转换为 Lambda 表达式。

Tips: 换句话说函数式接口就是 Lambda 表达式的类型。

在函数式接口中,单一方法的命名并不重要,只要方法签名和 Lambda 表达式的类型匹配即可。

Tips: 通常我们会为接口中的参数其一个有意义的名字来增加代易读性,便于理解参数的用途。

函数式接口有下面几个特点:

  1. 接口有且仅有一个抽象方法;
  2. 允许定义静态方法;
  3. 允许定义默认方法;
  4. 允许 java.lang.Object 中的 public 方法;
  5. 推荐使用 @FunctionInterface 注解(如果一个接口符合函数式接口的定义,加不加该注解都没有影响,但加上该注解可以更好地让编译器进行检查)。

我们来看函数式接口的例子:

实例演示
预览 复制
复制成功!
@FunctionalInterface
interface TestFunctionalInterface
{
    //抽象方法
    public void doTest();
    //java.lang.Object中的public方法
    public boolean equals(Object obj);
    public String toString();
    //默认方法
    public default void doDefaultMethod(){System.out.println("call dodefaultMethod");}
    //静态方法
    public static void doStaticMethod(){System.out.println("call doStaticMethod");}


    public static void main(String...s){
        //实现抽象方法
        TestFunctionalInterface test = ()->{
            System.out.println("call doTest");
        };
        //调用抽象方法
        test.doTest();
        //调用默认方法
        test.doDefaultMethod();
        //调用静态方法
        TestFunctionalInterface.doStaticMethod();
        //调用toString方法
        System.out.println(test.toString());
    }
}
运行案例 点击 "运行案例" 可查看在线运行效果

我们将得到如下结果:

call doTest
call dodefaultMethod
call doStaticMethod
com.github.x19990416.item.TestFunctionalInterface$$Lambda$1/0x00000008011e0840@63961c42
代码块
预览 复制
复制成功!

我们通过 toString 方法可以发现,test 对象被便已成为 TestFunctionalInterface 的一个 Lambda 表达式。

2. @FunctionalInterface

接下来我们再来看下 @FunctionalInterface注解的作用:

首先我们定义一个接口 TestFunctionalInterface,包含两个方法 doTest1doTest2

interfact TestFunctionalInterface{
    //一个抽象方法
    public void doTest1();
    //另一个抽象方法
    public void doTest2();
}
代码块
预览 复制
复制成功!

此时对于编译期而言我们的代码是没有任何问题的,它会认为这就是一个普通的接口。当我们使用 @FunctionalInterface 后:

//这是一个错误的例子!!!!
@FunctionalInterface
interfact TestFunctionalInterface{
    //一个抽象方法
    public void doTest1();
    //另一个抽象方法
    public void doTest2();
}
代码块
预览 复制
复制成功!

此时,会告诉编译器这是一个函数式接口,但由于接口中有两个抽象方法,不符合函数式接口的定义,此时编译器会报错:

Multiple non-overriding abstract methods found in interface
代码块
预览 复制
复制成功!

3. 常用的函数式接口

JDK 8 之后新增了一个函数接口包 java.util.function 这里面包含了我们常用的一些函数接口:

接口 参数 返回类型 说明
Predicate T boolean 接受一个输入参数 T,返回一个布尔值结果
Supplier None T 无参数,返回一个结果,结果类型为 T
Consumer T void 代表了接受一个输入参数 T 并且无返回的操作
Function<T,R> T R 接受一个输入参数 T,返回一个结果 R
UnaryOperator T T 接受一个参数为类型 T,返回值类型也为 T
BinaryOperator (T,T) T 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果

3.1 Predicate

条件判断并返回一个Boolean值,包含一个抽象方法 (test) 和常见的三种逻辑关系 与 (and) 、或 (or) 、非 (negate) 的默认方法。

Predicate 接口通过不同的逻辑组合能够满足我们常用的逻辑判断的使用场景。

实例演示
预览 复制
复制成功!
import java.util.function.Predicate;

public class DemoPredicate {
    public static void main(String[] args) {
        //条件判断
        doTest(s -> s.length() > 5);
        //逻辑非
        doNegate(s -> s.length() > 5);
        //逻辑与
        boolean isValid = doAnd(s -> s.contains("H"),s-> s.contains("w"));
        System.out.println("逻辑与的结果:"+isValid);
        //逻辑或
        isValid = doOr(s -> s.contains("H"),s-> s.contains("w"));
        System.out.println("逻辑或的结果:"+isValid);
    }

    private static void doTest(Predicate<String> predicate) {
        boolean veryLong = predicate.test("Hello World");
        System.out.println("字符串长度很长吗:" + veryLong);
    }

    private static boolean doAnd(Predicate<String> resource, Predicate<String> target) {
        boolean isValid = resource.and(target).test("Hello world");
        return isValid;
    }

    private static boolean doOr(Predicate<String> one, Predicate<String> two) {
        boolean isValid = one.or(two).test("Hello world");
        return isValid;
    }
    private static void doNegate(Predicate<String> predicate) {
        boolean veryLong = predicate.negate().test("Hello World");
        System.out.println("字符串长度很长吗:" + veryLong);
    }
}
运行案例 点击 "运行案例" 可查看在线运行效果

结果如下:

字符串长度很长吗:true
字符串长度很长吗:false
逻辑与的结果:true
逻辑或的结果:true
代码块
预览 复制
复制成功!

3.2 Supplier

用来获取一个泛型参数指定类型的对象数据(生产一个数据),我们可以把它理解为一个工厂类,用来创建对象。

Supplier 接口包含一个抽象方法 get,通常我们它来做对象转换。

实例演示
预览 复制
复制成功!
import java.util.function.Supplier;

public class DemoSupplier {
    public static void main(String[] args) {
        String sA = "Hello ";
        String sB = "World ";
        System.out.println(
                getString(
                        () -> sA + sB
                )
        );
    }

    private static String getString(Supplier<String> stringSupplier) {
        return stringSupplier.get();
    }
}
运行案例 点击 "运行案例" 可查看在线运行效果

结果如下:

Hello World 
代码块
预览 复制
复制成功!

上述例子中,我们把两个 String 对象合并成一个 String。

3.3 Consumer

与 Supplier 接口相反,Consumer 接口用于消费一个数据。

Consumer 接口包含一个抽象方法 accept 以及默认方法 andThen 这样 Consumer 接口可以通过 andThen 来进行组合满足我们不同的数据消费需求。最常用的 Consumer 接口就是我们的 for 循环,for 循环里面的代码内容就是一个 Consumer 对象的内容。

实例演示
预览 复制
复制成功!
import java.util.function.Consumer;

public class DemoConsumer {
    public static void main(String[] args) {
        //调用默认方法
        consumerString(s -> System.out.println(s));
        //consumer接口的组合
        consumerString(
                // toUpperCase()方法,将字符串转换为大写
                s -> System.out.println(s.toUpperCase()),
                // toLowerCase()方法,将字符串转换为小写
                s -> System.out.println(s.toLowerCase())
        );
    }

    private static void consumerString(Consumer<String> consumer) {
        consumer.accept("Hello");
    }

    private static void consumerString(Consumer<String> first, Consumer<String> second) {
        first.andThen(second).accept("Hello");
    }
}
运行案例 点击 "运行案例" 可查看在线运行效果

结果如下:

Hello
HELLO
hello
代码块
预览 复制
复制成功!

在调用第二个 consumerString 的时候我们通过 andThen 把两个 Consumer 组合起来,首先把 Hello 全部转变成大写,然后再全部转变成小写。

3.4 Function

根据一个类型的数据得到另一个类型的数据,换言之,根据输入得到输出。

Function 接口有一个抽象方法 apply 和默认方法 andThen,通过 andThen 可以把多个 Function 接口进行组合,是适用范围最广的函数接口。

实例演示
预览 复制
复制成功!
import java.util.function.Function;

public class DemoFunction {
    public static void main(String[] args) {
        doApply(s -> Integer.parseInt(s));
        doCombine(
                str -> Integer.parseInt(str)+10,
                i -> i *= 10
        );
    }

    private static void doApply(Function<String, Integer> function) {
        int num = function.apply("10");
        System.out.println(num + 20);
    }
    private static void doCombine(Function<String, Integer> first, Function<Integer, Integer> second) {
        int num = first.andThen(second).apply("10");
        System.out.println(num + 20);
    }
}
运行案例 点击 "运行案例" 可查看在线运行效果

结果如下:

30
220
代码块
预览 复制
复制成功!

上述四个函数接口是最基本最常用的函数接口,需要熟悉其相应的使用场景并能够熟练使用。 UnaryOperatorBinaryOperator 作用与 Funciton 类似,大家可以通过 Java 的源代码进一步了解其作用。

4. 小结

本节,我们主要阐述了函数式接口的定义以及其与 Lambda 表达式的关系。并对新增的 java.util.function 包中常用的函数式接口进行了解释。这些接口常用于集合处理中(我们将在后续的内容进一步学习),关于函数式接口主要记住一点,那就是:

接口有且仅有一个抽象方法