JDK新特性(四)——函数式接口

前言

函数式接口是JDK1.8推出的新特性之一,可以说函数式接口给Lambda表达式这种函数提供了简便的使用环境,同时ConsumerPredicateSupplier等抽象化的函数式接口也让我们的代码更加简洁,本篇文章将对函数式接口的定义和常用的函数式接口的使用进行讲解,希望能够对各位读者有所帮助。

如果你对Lambda表达式和方法引用没有了解,建议先去阅读本系列关于这两者的内容,再来阅读本文:
JDK新特性(一)——Lambda表达式
JDK新特性(三)——方法引用


一、函数式接口的概述

先说说函数式接口的定义

满足有且仅有一个抽象方法的接口,就属于函数式接口。我们还可以使用@FunctionalInterface注解来检测某个接口是否是函数式接口,如果不满足的话会编译失败。

@FunctionalInterface
public interface User {

    void save(String printStr);
}

注意,我们自己定义函数式接口的时候,@FunctionalInterface是可选的,即使不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。不过从代码规范性的角度出发,还是建议加上该注解

二、函数式接口的产生原因

我们在介绍常用的函数式接口之前,不妨先来想一下它们产生的原因。我们可以看一下下面这几个案例:

案例一:对函数式接口中带参有返回值的方法使用Lambda表达式进行实现
public interface Converter {

    int convert(String num);
}
public class MethodReferenceTest {

    public static void main(String[] args) {
        // 使用Lambda表达式创建实现子类
        //Converter converter = s -> Integer.parseInt(s);
        Converter converter = Integer::parseInt;
        // 使用方法引用
        int result = converter.convert("123");
    }
}
案例二:对函数式接口中无参有返回值的方法使用Lambda表达式进行实现
@FunctionalInterface
public interface RandomUtil {

    Integer getRandomNum();
}
public class MethodReferenceTest {

    public static void main(String[] args) {
        // 使用Lambda表达式创建实现子类
        RandomUtil randomUtil = () -> new Random().nextInt(30);
        int result = randomUtil.getRandomNum();
    }
}
案例三:对函数式接口中有参无返回值的方法使用Lambda表达式进行实现
@FunctionalInterface
public interface User {

    void save(String printStr);
}
public class MethodReferenceTest {

    public static void main(String[] args) {
        // 使用Lambda表达式创建实现子类
        User user = s -> System.out.println(s);
        // 使用方法引用方式实现子类
        // User user = System.out::println;
        user.save("hello , function interface");
    }
}

通过上面三个函数式接口案例,我们可以发现这三种接口方法分别代表了我们平常接口方法开发的类型:

1. 不需要参数,返回我们想要的类型数据
2. 方法接收参数实现特定的功能
3. 接收参数后方法返回我们想要的类型数据

但是,上面的案例似乎还可以进一步简化,我们会发现每当我们想要实现这样一个方法的时候,都要去创建一个函数式接口出来,需要的方法一多,我们就不得不需要创建许多的接口来满足需要了。
那么,有什么方法可以解决这个问题呢?
我们提出的这个问题,JDK早已提出了解决方案,JDK在1.8的时候还推出了SupplierConsumerFunction等常用的接口给我们使用。分别对应我们上面提到的三种情况,Suppliser生产者能提供我们想要的数据类型,对应上面的第一种接口方法类型,Consumer消费者提供accept消费方法,用于消费参数,对应上面的第二种接口方法类型;Function函数接口提供apply方法,用于接收参数并返回结果,对应上面的第三种接口方法类型。
也就是说,JDK把我们常用的接口方法类型进行了进一步的抽取,封装成了更加抽象的接口。从此,只要是符合上述条件的接口就可以不用再额外定义,只需要使用JDK定义好的方法就行。

三、常用的函数式接口

(一)生产者:Supplier

Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

我们可以看到Supplier的接口定义十分的简单,下面我们就来使用这个接口来简化第二节的案例吧

public class MethodReferenceTest {

    public static void main(String[] args) {
        Supplier<Integer> supplier = () -> new Random().nextInt(30);
        System.out.println(supplier.get());
    }
}

我们可以看到,使用了Supplier接口后,我们不需要再单独定义一个接口出来了。毕竟两者本质上都是提供某个指定类型数据的接口。

(二)消费者:Consumer

Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
方法名 说明
void accept(T t) 对给定的参数执行此操作
default Consumer andThen(Consumer after) 返回—个组合的Consumer,依次执行此操作,然后执行after操作

下面我们使用一下消费者接口来优化一下我们第二节的案例

public class MethodReferenceTest {

    public static void main(String[] args) {
        Consumer<String> consumer = System.out::println;
     // 也可以这样写 Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("hello consumer interface");
    }

}
(三)函数接口:Function

Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}
常用方法介绍
方法名 说明
R apply(T t) 将此函数应用于给定的参数
default Function andThen(Function after) 返回—个组合函数,首先将该函数应用于输入,然后将after函数应用于结果

接下来我们来使用函数接口优化一下我们第二节的案例

public class MethodReferenceTest {

    public static void main(String[] args) {
        Function<String,Integer> fun = s -> Integer.parseInt(s);
        System.out.println(fun.apply("123"));
    }
}
(四)布尔值判断:Predicate

Predicate接口通常用于判断参数是否满足指定的条件,简单理解的话,就是JDK把返回布尔类型数据的函数式接口进行抽取,封装成Predicate这种通用的函数式接口。值得一提的是,Predicate接口方法中还封装了提供与、或、非等逻辑判断方法供我们使用。

常用方法介绍
方法名 说明
boolean test(T t) 对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回—个布尔值
default Predicate negate() 返回—个逻辑的否定,对应逻辑非
default Predicate and(Predicate other) 返回—个组合判断,对应短路与
default Predicate or(Predicate other) 返回一个组合判断,对应短路或

下面我们就来使用Predicate接口来做一个小案例吧
现有字符串数组:{"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"},我们想要对数组中满足姓名长度大于2,且年龄大于32的条件的字符串进行过滤,要求我们通过Predicate接口来对实现上述的逻辑

public class MethodReferenceTest {

    public static void main(String[] args) {
        String[] arr = {"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"};
        List list = filterArr(arr, s -> s.split(",")[0].length() > 2, s -> Integer.parseInt(s.split(",")[1]) > 32);
        System.out.println(list);
    }

    private static List filterArr(String[] arr, Predicate<String> p1, Predicate<String> p2){
        List<String> list = new ArrayList<>();
        for(int i = 0 ; i<arr.length; i++){
            if(p1.and(p2).test(arr[i])){
                list.add(arr[i]);
            }
        }
        return list;
    }
}

至此,我们对于函数式接口的介绍就到这里了,函数式接口的理解并不难,关键在于掌握常用的几个函数式接口的应用场景,清楚其实际就是对各个类型的接口的共有特性进行抽取后的结果。如果有不了解的地方,欢迎留言

posted @ 2021-03-29 21:07  moutory  阅读(9)  评论(0编辑  收藏  举报  来源