Java基础加强:lambda 表达式之四大函数式接口

文章时效性提示

这是一篇发布于 1137 天前的文章,部分信息可能已发生改变,请注意甄别。

Java8 又称 Java 1.8 版本是自 Java 1.5 版本以来最大的一个版本变动,这一次带来了许多的新特性,首当其冲的莫过于 lambda 表达式,为了配合 lambda 表达式的使用,加入了 函数式 接口。

本文通过几个例子,描述 lambda 表达式结合函数式接口的基本使用方式。

在开始之前,请确定你已经了解并会使用 lambda 表达式

废话不多说,开始!

一、什么是函数式接口

正如其名,函数式接口其实就是表示为函数方式的接口,虽然是函数式,但依然还是一个接口。
这样讲可能有点拗口,通俗点来说,函数式接口就是 只有一个抽象方法的接口
如:

public interface Com{
boolean test();
}

接口 Com 可被称作一个函数式接口


二、函数式接口的构成

一个函数式接口通常来说必须满足以下两个必要条件

  • 首先,它必须是一个 标准的接口定义,不是注解,类,或者枚举。
  • 其次,它的接口体内有且仅有一个 抽象方法

接口内的方法默认都是 抽象方法,没有方法体。虽然 Java 1.8 引入了接口默认方法,其中可以有方法体,但其并不能成为抽象方法。所以,在函数式接口中可以存在多个方法,但 抽象方法 必须只有一个。

FunctionalInterface 注解

@FunctionalInterface 是 Java 1.8 引入的注解,是为了配合函数式接口、lambda 表达式而设计的,目的是检查某个接口是否满足函数式接口的定义

若在接口上标记该注解,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。总的来说,这个注解可以帮助我们检查接口定义是否符合函数式接口的规范。

泛型

由于函数式接口的特殊性,通常我们都会给其加上泛型。

所以,标准的函数式接口定义为:

//方式一:无默认方法
@FunctionalInterface
public interface Runn <T>{
void test(T a);
}

//方式二:有默认方法
@FunctionalInterface
public interface Tunn <T>{
void test(T a);

//无论有多少个默认方法
//只要满足只有一个抽象方法的接口就是函数式接口
default go(){
4//方法体
}

default to(int a){
//方法体
}
}

三、如何使用函数式接口

3.1 普通方式

和普通接口一样,函数式接口一样可以通过定义实现类或者匿名内部类的方式进行使用。

定义实现类方式:

public class Goo implements Runn<String>{

@Override
public void test(String a){
System.out.println(a);
}

public static void main(String[] args){
Goo go = new Goo();
go.test("定义实现类方式实现Runn");
}
}

匿名内部类方式:

Runn<String> runn = new Runn<String>(){
//必须实现Runn接口的方法
public void test(String a){
System.out.println(a);
}
}
runn.test("匿名内部类实现Runn");

但这些并不是我们的重点,关键看 lambda 表达式是如何使用函数式接口的。

3.2 lambda 表达式方式

Runn<String> runn = (a) -> {System.out.println(a)};
runn.test("lambda表达式方式");

可以看到,在使用 lambda 表达式之后,整个式子变得非常的简洁。可以说,函数式接口和 lambda 表达式是完全相互融合的。那么,这么简洁的代码是如何执行的呢?又或者说 lambda 表达式的工作又是什么呢?

事实上,lambda 表达式的工作分为 三个部分

  • 自动实现了等号左边的接口

    //Runn<String> runn =
    Runn<String> runn = new Goo<String>();
  • 将箭头前的参数作为实现的test方法中的参数

    //Runn<String> runn = (a) ->
    public void test(String a)
  • 将箭头后的语句作为实现的 test 方法中的方法体

    //Runn<String> runn = (a) -> {System.out.println(a)};
    public void test(String a){
    System.out.println(a);
    }

四、四大函数式接口

Java 1.8 引入函数式接口就是为了解决完全面向对象在某些领域内的痛点,而为了让开发者更高效的使用函数式接口,Java 1.8 还在 java.util.function 包下定义了许多内置的函数式接口,其中最常用的莫过于以下四种:

  • Consumer
  • Supplier
  • Function
  • Predicate

4.1 Consumer < T >:消费型接口

消费型接口即指该接口内的唯一抽象方法(void accept(T t) 方法) 只接受输入(有参数) ,没有返回值只进不出

/**
* 消费型接口,只输入(有参)不输出(无返回值)
* @FunctionalInterface
// 根据给定的参数 t 执行 accept 方法,无返回值.
// Params:t 输入的参数
public interface Consumer<T> {
void accept(T t);
}
*/
public void consumerInterfaceTest() {
/**
* lambda表达式方式
*/

//lambda 表达式实际上是自动实现了等号左边的接口
//然后再将箭头前的参数名作为实现的accept方法中的参数名
//箭头后的语句作为实现的accept方法中的方法体({}内的内容为 accept 方法的方法体)
Consumer<String> conn = (t) -> {
//方法体
System.out.println(t);
}
conn.accept("Consumer 消费型接口");
}
//输出:Consumer 消费型接口

4.2 Supplier < T >:供给型接口

供给型接口即指该接口内的唯一抽象方法 无输入(无参数) ,有返回值只出不进

/**
* 供给型接口,只输出(有返回值)不输入(无参)
* @FunctionalInterface
public interface Supplier<T> {
// 获取一个结果.
T get();
}
*/
public void suppierInerfaceTest() {
Supplier<String> sup = () -> {
//方法体
String a = "Supplier 供给型接口";
return a;
};
System.out.println(sup.get());
}
//输出:Supplier 供给型接口

4.3 Function < T, R > :函数型接口

函数型接口即指该接口内的唯一抽象方法 既有输入(有参数) ,又有返回值有出有进

/**
* 函数型接口,既输入又输出
@FunctionalInterface
public interface Function<T, R> {

// 用给定的参数 t 生成函数.
// @param t 函数的参数
// @return 函数的返回值
R apply(T t);
}
*/

public void functionInterfaceTest() {
Function<String, String> function = a -> {
//方法体
return a;
};
System.out.println(function.apply("Function 函数型接口"));
}
//输出:Function 函数型接口

4.4 Predicate < T >:断言型接口

断言型接口即指该接口内的唯一抽象方法 有输入(有参数) ,且返回值必须为布尔值有出有进

/**
* 断言型接口即指该接口内的唯一抽象方法有输入(有参数) ,
* 且返回值必须为布尔值。既出且进。
@FunctionalInterface
public interface Predicate<T> {

// 根据给定的参数执行 test 方法
// @param t 输入的参数
// @return {@code true} 返回布尔值,
// otherwise {@code false}
boolean test(T t);
}
*/
public void predicateInterfaceTest() {
Predicate<String> predicate = (str) ->{
//方法体
if(str.equals("1"))
return true;
return false;
};

if(predicate.test("1"))
System.out.println("Predicate 断言型接口测试");
}
//输出:Predicate 断言型接口测试

五、其他函数式接口

除了上述 4 种类型的接口外,还有其他的一些接口供我们使用:

  • BiFunction < T, U, R > 参数类型有2个,为 T,U,返回值为 R,其中方法为 R apply(T t, U u)

  • UnaryOperator < T > (Function子接口) 参数为T,对参数为 T 的对象进行一元操作,并返回 T 类型结果,其中方法为 T apply(T t)

  • BinaryOperator < T > (BiFunction子接口) 参数为T,对参数为 T 得对象进行二元操作,并返回 T 类型得结果,其中方法为 T apply(T t1, T t2)

  • BiConsumer <T, R> 为 void accept(T t, U u)

  • ToIntFunction < T >、ToLongFunction < T >、ToDoubleFunction < T > 参数类型为 T,返回值分别为 int,long,double,分别计算 int,long,double 的函数。

  • IntFunction < R >、LongFunction < R >、DoubleFunction < R > 参数分别为 int,long,double,返回值为 R。

    等等…….

以上就是 Java 8 内置的核心函数式接口,其中包括了大部分的方法类型,可以在使用的时候根据不同的使用场景去选择不同的接口使用。

六、参考链接

Java新特性-四大函数式接口