函数式接口

什么是函数式接口

函数式接口(Functional Interface)就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 Lambda 表达式。

我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

定义一个函数式接口
package demo1;

@FunctionalInterface
public interface MyFunInterface {
    public abstract void show();
    
    default void showInit(){}
}

如何理解函数式接口

Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即Java不但可以支持OOP还可以支持OOF(面向函数编程)
在函数式编程语言当中,函数被当做一等公民对待。

在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。 在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。

JDK 1.8 之前已有的函数式接口:

java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

JDK 1.8 新增加的函数接口:

java.util.function

Java 内置四大核心函数式接口

JAVA8在java.util.function定义了大量的函数式接口,核心函数式接口主要有一下四个:

Supplier接口

Interface Supplier:包含了一个无参的方法

  • T get():获取结果。
  • 该方法无需要参数,它会按照某种逻辑(由Lambda表达式实现)返回一个结果
  • Supplier接口也被称为生产接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用,但不传入参数。

声明如下:

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

案例1

import java.util.function.Supplier;

/**
 * 理解输出Supplier函数式接口
 * 以返回一个字符串“123”为例
 *
 * @author Anna.
 * @date 2024/4/3 12:37
 */
public class SupplierDemo3 {
    public static void main(String[] args) {
        // 通过实现Supplier接口调用get()方法实现
        Supplier<String> stringSupplier = new Supplier<String>() {
            @Override
            public String get() {
                return "123";
            }
        };
        System.out.printf("实现Supplier接口的Supplier:%s%n", stringSupplier);
        System.out.printf("通过实现Supplier接口调用get()方法实现:%s%n", stringSupplier.get());

        // 通过函数式接口传入Lambda表达式的方式实现
        String str = getStringBySupplier(() -> "123");
        System.out.printf("通过函数式接口传入Lambda表达式的方式实现:%s%n", str);
    }

    /**
     * 定义一个返回类型是String的Supplier接口函数调用方法
     *
     * @author Anna.
     * @date 2024/4/3 12:39
     */
    public static String getStringBySupplier(Supplier<String> supplier) {
        System.out.printf("定义方法中的Supplier:%s%n", supplier);
        return supplier.get();
    }
}

02-JDK新特性-函数式接口-LMLPHP

函数式接口关心入参及返回值,执行过程可以看着将Lambda表达作为参数进行传递,具体实现有由Lambda表达式内部方法体完成。

通过上述结果我们可以看出,实现Supplier接口的Supplier是SupplierDemo3实例的一个内部类,而定义方法中的Supplier返回的却是一个Lambda。

案例2

package demo2;

import java.util.function.Supplier;

/**
 *  使用Supplier<T>定义一个泛型方法,等到一个同类型的返回数据
 *
 * @author Anna.
 * @date 2024/4/1 23:57
 */
public class SupplierDemo {
    public static void main(String[] args) {
        // 匿名内部类方法实现
        String str = null;
        str = get(new Supplier<String>() {
            @Override
            public String get() {
                return "Hello".toUpperCase();
            }
        });
        System.out.printf("匿名内部类方法实现:%s%n",str);

        // Lambda表达式实现
        str = get(() -> {return "Hello".toUpperCase();});
        System.out.printf("Lambda表达式实现:%s%n",str);

        // Lambda表达式简写实现
        str = get(() -> "Hello".toUpperCase());
        System.out.printf("Lambda表达式简写实现:%s%n",str);

        // 方法引用实现
        str = get("Hello"::toUpperCase);
        System.out.printf("方法引用实现:%s%n",str);
    }

    /**
     * 定义一个泛型方法,等到一个同类型的返回数据
     *
     * @param supplier
     * @return T
     * @author Anna.
     * @date 2024/4/2 0:00
     */
    public static <T> T get(Supplier<T> supplier){
        return supplier.get();
    }
}

02-JDK新特性-函数式接口-LMLPHP

案例3

package demo2;

import java.util.function.Supplier;

/**
 * 使用Supplier<T>定义一个泛型方法,获取数组中的最大值
 *
 * @author Anna.
 * @date 2024/4/1 23:57
 */
public class SupplierDemo2 {
    public static void main(String[] args) {
        // 定义一个数组
        int[] arr = {123, 12, 123, 233, 1231};
        Integer max = get(() -> {
            int rtn = 0;
            for (int item : arr) {
                if (item > rtn) {
                    rtn = item;
                }
            }
            return rtn;
        });

        System.out.println("max = " + max);

        // 抽取方法
        max = get(() -> getInteger(arr));

        System.out.println("max = " + max);
    }

    /**
     * 定义一个泛型方法,等到一个同类型的返回数据
     *
     * @param supplier
     * @return T
     * @author Anna.
     * @date 2024/4/2 0:00
     */
    public static <T> T get(Supplier<T> supplier) {
        return supplier.get();
    }

    /**
     * 提取成一个方法
     *
     * @param arr
     * @return java.lang.Integer
     * @author Anna.
     * @date 2024/4/2 9:22
     */
    private static Integer getInteger(int[] arr) {
        int rtn = 0;
        if (arr != null && arr.length > 0) {
            for (int item : arr) {
                if (item > rtn) {
                    rtn = item;
                }
            }
        }
        return rtn;
    }

}

02-JDK新特性-函数式接口-LMLPHP

Consumer接口

Interface Consumer:包含两个方法:

  • void accept(T t):对给定的参数执行操作
  • default Consumer andThen(Consumer<? super T> after):Consumer组合依次执行andThen操作,然后执行after操作。

    Consumer接口也被称为消费型接口,它消费的数据类型由泛型指定,不返回任何结果。

案例1

package demo3;

import java.util.function.Consumer;

/**
 * String[] strArray = {"张三,数学,30","李四,语文,40","王五,体育,100"};
 * 字符串数组中包含多条信息,请按照格式,“姓名:XXX,科目:XXX,分数:XXX”的格式将信息打印出来
 * 要求:
 * 把打印姓名的动作作为第一个Consumer接口的Lambda示例
 * 把打印科目的动作作为第二个Consumer接口的Lambda示例
 * 把打印分数的动作作为第三个Consumer接口的Lambda示例
 * 将三个Consumer接口按照顺序组合到一起使用
 *
 * @author Anna.
 * @date 2024/4/3 12:08
 */
public class ConsumerDemo {

    public static void main(String[] args) {
        String[] strArray = {"张三,数学,30", "李四,语文,40", "王五,体育,100"};
        print(strArray,
                s -> System.out.print("姓名:" + s.split(",")[0] + ","),   // 第一个Consumer接口的Lambda
                s -> System.out.print("科目:" + s.split(",")[1] + ","),   // 第二个Consumer接口的Lambda
                s -> System.out.println("分数:" + s.split(",")[2]));     // 第三个Consumer接口的Lambda
    }

    public static void print(String[] arr, Consumer<String> com1, Consumer<String> com2, Consumer<String> com3) {
        for (String str : arr) {
            // 链式调用 等价于
            //  com1.accept(str);
            //  com2.accept(str);
            //  com3.accept(str);
            com1.andThen(com2).andThen(com3).accept(str);

        }
    }
}

02-JDK新特性-函数式接口-LMLPHP

案例2

package demo3;

import java.util.function.Consumer;

/**
 * 定义一个Config对象,通过函数式接口,完成数据的初始化
 *
 * @author Anna.
 * @date 2024/4/3 12:08
 */
public class ConsumerConfig {

    private String ip;

    private Integer port;

    /**
     * 初始化对象
     *
     * @param consumer
     * @return void
     * @author Anna.
     * @date 2024/4/3 12:25
     */
    public void init(Consumer<ConsumerConfig> consumer) {
        consumer.accept(this);
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    @Override
    public String toString() {
        return "ConsumerConfig{" +
                "ip='" + ip + '\'' +
                ", port=" + port +
                '}';
    }

    public static void main(String[] args) {
        ConsumerConfig config = new ConsumerConfig();
        System.out.println("未初始化之前:" + config);
        config.init((consumer) -> {
            consumer.setIp("127.0.0.1");
            consumer.setPort(8080);
        });
        System.out.println("初始化之后:" + config);
    }
}

02-JDK新特性-函数式接口-LMLPHP

Predicate接口

Interface Predicate:常用的四个方法:

  • boolean test(T t):对给定的参数进行判断(判断逻辑有Lambda表达式实现),返回一个布尔值
  • default Predicate and(Predicate<? super T> other):返回一个逻辑的否定,对应逻辑非(!),源码如下
default Predicate<T> negate() {return (t) -> !test(t);}
  • default Predicate negate():返回一个组合判断,对应短路与(&&),源码如下
default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}
  • default Predicate or(Predicate<? super T> other):返回一个组合判断,对应逻辑或(||),源码如下
default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) || other.test(t);
}

案例1

package demo4;

import java.util.function.Predicate;

/**
 * 使用函数式接口Predicate,完成以下校验
 * 1 判断一个字符串长度是否大于5
 * 2 判断一个字符串长度小于5且已S开头
 * 3 判断一个字符串小于等于5
 *
 * @author Anna.
 * @date 2024/4/3 14:10
 */
public class PredicateDemo {
    public static void main(String[] args) {
        // 判断一个字符串长度是否大于5
        test01("12321", s -> s != null && s.length() > 5);
        // 判断一个字符串长度小于5且已S开头
        test02(null, s -> s != null && s.length() > 5, s -> s.startsWith("S"));
        // 判断一个字符串小于等于5
        test03("S2321", s -> s != null && s.length() > 5);
    }

    public static void test01(String str, Predicate<String> predicate) {
        System.out.println(predicate.test(str));
    }

    public static void test02(String str, Predicate<String> pdc1, Predicate<String> pdc2) {
        System.out.println(pdc1.and(pdc2).test(str));
    }

    public static void test03(String str, Predicate<String> pdc1) {
        System.out.println(pdc1.negate().test(str));
    }
}

02-JDK新特性-函数式接口-LMLPHP

Function接口

Interface Function<T,R>:常用的两个方法:

  • R apply(T t):将此函数应用于给定的参数
  • default Function<T,V> andThen(Function<? super R,? extends V> after):返回一个组合函数,首先将该函数应用于输出,然后将after函数应用于结果。源码如下:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}
  • Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),并返回一个新的值。

案例1

package demo5;

import java.util.function.Function;

/**
 * 使用Function函数式接口:
 *      1 将一个字符串数字,转换成int类型,输出在控制台
 *      2 将一个字符串数字,转换成int类型,然后使用第二个Lambda表达式完成,加20,最后输出在控制台
 * @author Anna.
 * @date 2024/4/3 14:59
 */
public class FunctionDemo {
    public static void main(String[] args) {
        // 将一个字符串数字,转换成int类型,输出在控制台
        print("213", Integer::parseInt);

        // 将一个字符串数字,转换成int类型,然后使用第二个Lambda表达式完成,加20,最后输出在控制台
        printAdd("100",Integer::parseInt,s -> s + 20);
    }

    public static void print(String str, Function<String,Integer> func){
        System.out.println(func.apply(str));
    }

    public static void printAdd(String str, Function<String,Integer> func1,Function<Integer,Integer> func2){
        System.out.println(func1.andThen(func2).apply(str));
    }
}

02-JDK新特性-函数式接口-LMLPHP

gitee源码

04-03 20:47