四时宝库

程序员的知识宝库

三、java函数式编程-函数接口(函数接口 java)

在前两章学习了 Lambda 表达式的写法和Stream的基本操作方法,可以囫囵吞枣简单的写写代码了。那么不理解的地方就是,为什么可以将Lambda 表达式作为参数传递?为什么Lambda 表达式固定的括号里不带参数,或者带参数?下面一步一步地进行讲解。

在 Java 里,所有方法参数都有固定的类型,如下代码

public int add(int x, int y) {
    return x + y;
}
// 调用方法
add(2, 3);

代码中参数x和y都是int类型,函数调用时传入两个int数值就可以了,那么函数调用传入Lambda 表达式,如下代码所示

new Thread(() -> System.out.println("函数式编程")).start();

代码里的Lambda 表达式是什么类型呢?看一下java源代码中Thread的定义

// Thread定义
public Thread(Runnable target) {
    this(null, target, "Thread-" + nextThreadNum(), 0);
}
// Runnable定义
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

代码中Runnable是接口,该接口标志了@FunctionalInterface注解,可以看出@FunctionalInterface注解的接口和Lambda 表达式紧密联系的,Runnable接口与Lambda 表达式写法转换如下所示

// Thread传入Runnable接口的匿名内部类
new Thread(new Runnable() {
    public void run() {
        System.out.println("函数式编程");
    }
}).start();
// 转换成Lambda表达式步骤
// 1、Thread构造方法只能传入Runnable接口,所以匿名实现类new Runnable(){}省略
// 2、Runnable接口只有一个接口run方法,所以public void run省略
// 3、run方法没有参数,所以只留下(),加上->分割符区分参数和代码块
// 转成变成Lambda表达式如下
new Thread(() -> {System.out.println("函数式编程");}).start();
// 代码块里面只有一句代码,所以省略代码块{}部分,最终变成
new Thread(() -> System.out.println("函数式编程")).start();

再看一下带参数的例子,比如java源代码Stream中的filter方法定义,代码如下

// filter方法传入Predicate接口
Stream<T> filter(Predicate<? super T> predicate);
// Predicate接口定义
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Predicate接口同样标志了@FunctionalInterface注解,Predicate接口与Lambda 表达式写法转换如下所示

// filter方法传入Predicate接口的匿名内部类
Stream.of("nodejs", "java", "python", "javascript")
        .filter(new Predicate<String>() {
            public boolean test(String s) {
                return s.startsWith("java");
            }
        });
// 转换成Lambda表达式步骤
// 1、filter方法只能传入Predicate接口,所以匿名实现类new Predicate<String>(){}省略
// 2、Predicate接口只有一个接口test方法,所以public boolean test省略
// 3、test方法有参数,所以保留(String s),加上->分割符区分参数和代码块
// 转成变成Lambda表达式如下
Stream.of("nodejs", "java", "python", "javascript")
        .filter((String s) -> {
                return s.startsWith("java");
            }
        );
// 代码块里面只有一句代码,且返回值也是同样的代码,可以省略代码块{}部分
Stream.of("nodejs", "java", "python", "javascript")
        .filter((String s) -> s.startsWith("java"));
// 定义参数类型String可省略,并且只有一个参数,()也可省略,但如果是多个参数就必须加上(),逗号分隔参数,最终变成如下代码
Stream.of("nodejs", "java", "python", "javascript")
        .filter(s -> s.startsWith("java"));

如果代码使用了已经定义好的方法,且此方法引用仅仅涉及单一方法,Lambda表达式提供更快捷的写法,如下代码

Stream.of("nodejs", "java", "python", "javascript")
        .filter(s -> s.isBlank());
// String::isBlank方法引用就是Lambda表达式s -> s.isBlank()的快捷写法
Stream.of("nodejs", "java", "python", "javascript")
        .filter(String::isBlank);

综合上面Thread和Stream这两个例子中,匿名内部类一步一步转换成Lambda 表达式看出,@FunctionalInterface函数接口就是Lambda 表达式的类型,并且@FunctionalInterface注解的接口只有一个可以实现的抽象方法。

下面列举的是6个基本的函数接口,每个函数接口都有固定的用法

函数接口

抽象方法

接口使用描述

方法引用示例代码

UnaryOperator<T>

T apply(T t)

对象处理后返回同类型对象

String::toLowerCase

BinaryOperator<T>

T apply(T t1, T t2)

两个同类型对象处理后变成一个对象

Integer::sum

Function<T, R>

R apply(T t)

对象处理后变成另外一个对象

Integer::toBinaryString

Predicate<T>

boolean test(T t)

对象判断

String::isEmpty

Supplier<T>

T get()

生成一个对象

LocalDate::now

Consumer<T>

void accept(T t)

消费对象

System.out::println

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接