• 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);
    }
  • 相关阅读:
    Nginx负载均衡:分布式/热备Web Server的搭建
    CentOS6.6 32位 Minimal版本纯编译安装Nginx Mysql PHP Memcached
    windows下nginx安装、配置与使用
    Redis基本操作——List
    MongoDB aggregate 运用篇 个人总结
    构建一个较为通用的业务技术架构
    2016年31款轻量高效的开源JavaScript插件和库
    正则表达式
    前端学习路线
    可变参数
  • 原文地址:https://www.cnblogs.com/Dm920/p/13363932.html
Copyright © 2020-2023  润新知