Java8 函数式接口

Posted Lam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8 函数式接口相关的知识,希望对你有一定的参考价值。

1. 概述

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

提到函数式接口肯定少不了 Lambda 表达式,函数式接口可以隐式的转换为 Lambda 表达式

我们可以选择向各种各样的方法和构造函数传递 Lambda 表达式,包括在 Java 8 之前创建的一些方法和构造函数。因为 Lambda 表达式在 Java 中表示为函数接口。

2. 什么是函数式接口?

先来看看传统的创建线程是怎么写的

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("t1");
    }
});
t1.start();

再来看看使用了函数式接口是怎么写的

Thread t2 = new Thread(() -> System.out.println("函数式接口"));
t2.start();

Runnable 接口直接可以使用 Lambda 表达式来编写,这是因为 Runnable 接口是一个函数式接口,来看看 Runnable 的源码。

@FunctionalInterface
public interface Runnable {

    public abstract void run();
    
}

发现该接口加上了函数式接口的定义注解: @FunctionalInterface ,表明该接口是一个函数式接口。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
    
}

函数式接口:简单地说,就是 Java 通过实现接口来间接性的将代码段传入使用。

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 8 专门新增的函数式接口:function

3. function

函数式接口规范:

1、@FunctionalInterface 标识为一个函数式接口只能用在只有一个抽象方法的接口上。

2、接口中的静态方法、默认方法、覆盖了 Object 类的方法都不算抽象方法。

3、@FunctionalInterface 注解不是必须的,如果该接口只有一个抽象方法可以不写,它默认就符合函数式接口,但建议都写上该注解,编译器会检查该接口是否符合函数式接口的规范

任何满足单一抽象方法法则的接口,都会被自动视为函数接口。这包括 Runnable Callable 等传统接口,以及自己构建的自定义接口。

java.util.function 包中最常用的接口包括有四个:Consumer<T>Supplier<T>Function<T> Predicate<T>

4. 四大函数式接口

① Consumer:消费型接口

顾名思义:只进不出

java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。

public class ConsumerDemo {

    /**
     * 使用Consumer接口消费字符串
     *
     * @param str      - 传递一个字符串
     * @param consumer - 传递Consumer接口,泛型使用String
     */
    public static void method1(String str, Consumer<String> consumer) {
        consumer.accept(str);
    }

    /**
     * 将两个Consumer接口组合到一起,再对数据进行消费
     *
     * @param str   - 传递一个字符串
     * @param cons1 - 连接con2参数
     * @param cons2 - 传递Consumer接口,泛型使用String
     */
    public static void method2(String str, Consumer<String> cons1, Consumer<String> cons2) {
        cons1.andThen(cons2).accept(str);
    }

    public static void main(String[] args) {

        // 调用method方法,传递字符串,方法的另一个参数是Consumer接口,
        // Consumer是一个函数式接口,所以可以传递Lambda表达式
        method1("consumer", (String str) -> {
                    System.out.println(str.toUpperCase()); // 消费方式:把字符串转换为大写输出
                }
        );
        // 优化Lambda
        method1("consumer", str -> System.out.println(str.toUpperCase()));

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

        // 调用method方法,传递一个字符串,两个Lambda表达式
        method2("jjjjjjjjj_CCCCCCCC", (str) -> {
            System.out.println(str.toUpperCase()); // 消费方式:把字符串转换为大写输出
        }, (str) -> {
            System.out.println(str.toLowerCase()); // 消费方式:把字符串转换为小写输出
        });
        // 优化Lambda
        method2("EEEEEEEEEEEEE_ccccccccccccccc", str -> System.out.println(str.toUpperCase()), str -> System.out.println(str.toLowerCase()));
    }
}

② Supplier:供给型接口

顾名思义:只出不进

1. java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

2. Stream流中 forEach(Supplier函数型接口)

public class SupplierDemo {

    /**
     * 取一个泛型参数指定类型的对象数据
     *
     * @param supplier - Supplier<String>接口
     * @return - 泛型执行String,get方法就会返回一个String
     */
    public static String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    /**
     * 求数组元素最大值
     *
     * @param supplier - Supplier<Integer>接口
     * @return - 获取int类型数组中元素的最大值
     */
    public static Integer getMax(Supplier<Integer> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {

        // 调用getString方法,方法的参数Supplier是一个函数式接口,可以传递Lambda表达式
        System.out.println(
                getString(() -> {
                    return "啊哈";
                })
        );
        // 优化Lambda表达式
        System.out.println(
                getString(() -> "呵呵")
        );

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

        int[] arr = {188, 210, 52, 263, 66, 312, 75, 29, 125, 55};
        // 调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
        int maxValue = getMax(
                () -> {
                    int max = arr[0]; // 定义一个变量,把数组中的第一个元素赋值给该变量,记录数组中元素的最大值
                    for (int i : arr) { // 遍历数组,获取数组中的其他元素
                        if (i > max) { // 使用其他的元素和最大值比较
                            max = i; // 如果i大于max,则替换max作为最大值
                        }
                    }
                    return max; // 返回最大值
                }
        );
        System.out.println("数组中元素的最大值是:" + maxValue);
    }
}

③ Function:函数式接口

顾名思义:y=fun(x),做一个类型转换

1. java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

2. Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。

3. 使用的场景例如:将 String 类型转换为 Integer 类型。Stream 中 map(Function函数型接口。

public class FunctionDemo {

    /**
     * 把字符串类型的整数,转换为Integer类型的整数返回
     *
     * @param str      - 字符串类型的整数
     * @param function - Function接口,泛型使用<String,Integer>
     */
    public static void change1(String str, Function<String, Integer> function) {
        //Integer in = function.apply(str);
        int in = function.apply(str);//自动拆箱 Integer->int
        System.out.println(in);
    }

    /**
     * 将字符串类型的整数转换为Integer类型进行运算后(x<<1),再把结果转换为字符串进行输出
     *
     * @param str       - 字符串类型的整数
     * @param function1 - 将字符串整数转换为Integer类型
     * @param function2 - 将Integer类型转换为字符串整数
     */
    public static void change2(String str, Function<String, Integer> function1, Function<Integer, String> function2) {
        String result = function1.andThen(function2).apply(str);
        System.out.println(result);
    }

    public static void main(String[] args) {

        // 调用change方法,传递字符串类型的整数,和Lambda表达式
        change1("1234", (str) -> {
            return Integer.parseInt(str);
        });

        // 优化Lambda
        change1("4321", str -> Integer.parseInt(str));

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

        change2("8", (str) -> {
            return Integer.parseInt(str) << 1; // 把字符串转换为整数后左移一位
        }, (i) -> {
            return String.valueOf(i); // 把整数转换为字符串
        });

        // 优化Lambda
        change2("16", s -> Integer.parseInt(s) << 1, i -> String.valueOf(i));
    }
}

④ Predicate:断定型接口

顾名思义:断定是否

有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean 值结果。这时可以使用java.util.function.Predicate<T> 接口。接口中包含一个抽象方法: boolean test(T t)

2. Stream 流中 filter(Predicate断定型接口)

public class PredicateDemo {

    /**
     * 对字符串进行校验
     *
     * @param str       - String类型的字符串
     * @param predicate - Predicate接口,泛型使用String
     * @return - 对字符串进行判断,并把判断的结果返回
     */
    public static boolean checkString1(String str, Predicate<String> predicate) {
        return predicate.test(str);
    }

    /**
     * 连接多个判断的条件,模拟逻辑与 (&&)
     *
     * @param str - String类型的字符串
     * @param p1  - 判断字符串的长度
     * @param p2  - 判断字符串中是否包含
     * @return - 两个结果为true返回true,有一个false返回false
     */
    public static boolean checkString2(String str, Predicate<String> p1, Predicate<String> p2) {
        return p1.and(p2).test(str);
    }

    public static void main(String[] args) {

        // 调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
        System.out.println("abcde.length() > 5 = " +
                checkString1("abcde", (str) -> {
                    return str.length() > 5; // 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
                })
        );
        // 优化Lambda
        System.out.println("123456.length() > 5 = " +
                checkString1("123456", str -> str.length() > 5)
        );

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

        // 调用checkString方法,参数传递字符串和两个Lambda表达式
        System.out.println(
                checkString2("abcde", (str) -> {
                    return str.length() >= 5;  //判断字符串的长度是否大于等于5
                }, (str) -> {
                    return str.contains("a"); // 判断字符串中是否包含a
                })
        );
        // 优化Lambda
        System.out.println(
                checkString2("qwertasd", str -> str.length() > 6, str -> str.contains("qwe"))
        );
    }
}

5. 自定义接口

如果哪一天,你觉得它的接口已经无法满足你的需求时,那么,你就能自定义接口了,hhh,Java 8 很友好,知道你想要,满足你的个性化定制。

我们只需要做两件事:

  1. 使用 @FunctionalInterface 注释该接口,这是 Java 8 对自定义函数接口的约定。
  2. 确保该接口只有一个抽象方法。

仔细观察 java.util.function 的接口,也一样,都做了两件事,自定义就是那么简单。

使用 @FunctionalInterface 注释可以确保,如果在未来更改该接口时意外违反抽象方法数量规则,您会获得错误消息。

实列就不写了,因为不是很难理解,我们参考参考 Function<T,R> 

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

以上是关于Java8 函数式接口的主要内容,如果未能解决你的问题,请参考以下文章

Java8函数式接口简介

Java Lambda

Java8的函数式编程怎么样?

从代码重构角度聊一聊Java8的函数式接口

Java8函数式接口以及lambda表达式实践

Java8函数式接口以及lambda表达式实践