• Java教程:一文详解函数式编程


    看懂了这篇-你就懂了函数式接口

    函数式编程是一种编程规范或一种编程思想,简单可以理解问将运算或实现过程看做是函数的计算。 Java8为了实现函数式编程,提出了3个重要的概念:Lambda表达式、方法引用、函数式接口。现在很多公司都在使用lambda表达式进行代码编写,甚至知名的Java的插件也都在Lambda,比如数据库插件MybatisPlus。Lambda表达式的使用是需要函数式接口的支持,即lambda表达式的核心就是使用大量的函数式接口。本文带领大家全面了解函数式接口的定义和使用。

    一、文章导读

    • 函数式接口概述
    • 自定义函数式接口
    • 常用函数式接口
    • 函数式接口的练习

    二、函数式接口概述

    1.函数式接口定义

    如果接口里只有一个抽象方法,那么就是函数式接口,可以使用注解(@FunctionalInterface)检测该接口是否是函数式接口,即只能有一个抽象方法。

    注意事项

    函数式接口里可以定义默认方法:默认方法有方法体,不是抽象方法,符合函数式接口的定义要求。
    函数式接口里可以定义静态方法:静态方法也不是抽象方法,是一个有具体方法实现的方法,同样也符合函数式接口的定义的。
    函数式接口里可以定义Object里的public方法(改成抽象方法):虽然它们是抽象方法,却不需要覆盖重写,因为所有接口的实现类都是Object类的子类,而在Object类中有这些方法的具体的实现。
    
    • 1
    • 2
    • 3

    2.函数式接口格式

    修饰符 interface 接口名称 {
        //抽象方法
        public abstract 返回值类型 方法名称(可选参数信息);
        //默认方法
        public default 返回值类型 方法名称(可选参数信息) {
            //代码...        
        }
        //静态方法
        public static 返回值类型 方法名称(可选参数信息) {
            //代码...        
        }
        //Object类的public方法变成抽象方法
        public abstract boolean equals(Object obj);
        public abstract String toString();    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    三、自定义函数式接口

    1.自定义函数式接口举例

    由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单:

    @FunctionalInterface
    public interface MyFunctionalInterface {
        //抽象方法
        public abstract void method();
        //Object类的public方法变成抽象方法
        public abstract boolean equals(Object obj);
        public abstract String toString();
        //默认方法
        public default void show(String s) {
            //打印小写
            System.out.println(s.toLowerCase());
        }
        //静态方法
        public static void print(String s) {
            //打印大写
            System.out.println(s.toUpperCase());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.自定义函数式接口的应用

    对于刚刚定义好的MyFunctionalInterface函数式接口,典型使用场景就是作为方法的参数:

    public class Demo01FunctionalInterface {
        public static void main(String[] args) {
            // 调用使用函数式接口的方法
            show(()->{
                System.out.println("Lambda执行了");
            });
        }
        //定义方法使用函数式接口作为参数
        public static void show(MyFunctionalInterface mfi) {
            //调用自己定义的函数式接口
            mfi.method();
            String s = mfi.toString();
            System.out.println(s);
            boolean result = mfi.equals(mfi);
            System.out.println(result);
            mfi.show("world");
            MyFunctionalInterface.print("function");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.运行结果:

    Lambda执行了
    Demo01FunctionalInterface$$Lambda$1/1078694789@3d075dc0
    true
    world
    FUNCTION
    
    • 1
    • 2
    • 3
    • 4
    • 5

    四、常用函数式接口

    前面我们自己定义了一个函数式接口,对于一些常用的函数式接口,每次自己定义非常麻烦。JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在`java.util.function`包中被提供。这样的接口有很多,下面是最简单的几个接口及使用示例。
    
    • 1

    1. Supplier接口

    java.util.function.Supplier接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

    1.1.抽象方法 : get

    Supplier`接口中包含一个抽象方法` T get(): 用来获取一个泛型参数指定类型的对象数据。	
    
    • 1
    public class Demo02Supplier {
        public static void main(String[] args) {
            int num = getNum(() -> {
                return new Random().nextInt();
            });
            System.out.println(num);
        }
        public static int getNum(Supplier<Integer> supplier) {
            int num = supplier.get();
            return num;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.2.求集合元素最大值

    使用Supplier接口作为方法参数类型,通过Lambda表达式求出List集合(存储int数据)中的最大值。提示:接口的泛型请使用java.lang.Integer类。

    代码示例:

    public class Demo03Supplier {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            Collections.addAll(list,10,8,20,3,5);
            printMax(()->{
                return Collections.max(list);
            });
        }
    
        private static void printMax(Supplier<Integer> supplier) {
            int max = supplier.get();
            System.out.println(max);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. Consumer接口

    java.util.function.Consumer接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。

    2.1.抽象方法:accept

    Consumer接口中包含抽象方法void accept(T t): 消费一个指定泛型的数据。

    代码示例:

    import java.util.function.Consumer;
    //接收一个输入参数x,把x的值扩大2倍后,再+3做输出
    //类似于数学中的函数: f(x) = 2*x + 3
    public class Demo04Consumer {
        public static void main(String[] args) {
            int x = 3;
            consumeIntNum(x,(Integer num)->{
                System.out.println(2*num+3);
            });
    
        }
        /*
        	定义方法,使用函数式接口Consumer作为方法参数
        */
        private static void consumeIntNum(int num,Consumer<Integer> function ) {
            function.accept(num);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.2.默认方法:andThen

    如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen。下面是JDK的源代码:

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
    
    • 1
    • 2
    • 3
    • 4

    备注:java.util.ObjectsrequireNonNull静态方法将会在参数为null时主动抛出NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
    andThen是默认方法,由Consumer的对象调用,而且参数和返回值都是Consumer对象

    要想实现组合,需要两个或多个Lambda表达式即可,而andThen的语义正是“一步接一步”操作。例如两个步骤组合的情况:

    代码示例:

    //接收一个字符串,先按照大写打印,再按照小写打印
    /*
    	toUpperCase(): 把字符串变成大写
        toLowerCase(): 把字符串变成小写
     */
    public class Demo05Consumer {
        public static void main(String[] args) {
            String s = "Hello";
            //lambda标准格式
            fun(s, (String str) -> {
                System.out.println(s.toUpperCase());
            }, (String str) -> {
                System.out.println(s.toLowerCase());
            });
        }
    
        /*
            定义方法,参数是Consumer接口
            因为要消费两次,所以需要两个Consumer接口作为参数
         */
        public static void fun(String s, Consumer<String> con1, Consumer<String> con2) {
            //先消费一次
            con1.accept(s);
            //再消费一次
            con2.accept(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。但是我们却没有使用andThen方法,其实我上面的写法,就是andThen底层的代码实现。

    为了方便大家理解,下面我们使用andThen方法进行演示。

    public class Demo06Consumer {
        public static void main(String[] args) {
            String s = "HelloWorld";
            //2.lambda标准格式
            fun(s, (String str) -> {
                System.out.println(s.toUpperCase());
            }, (String str) -> {
                System.out.println(s.toLowerCase());
            });
        }
    
        /*
            定义方法,参数是Consumer接口
            因为要消费两次,所以需要两个Consumer接口作为参数
         */
        public static void fun(String s, Consumer<String> con1, Consumer<String> con2) {
    
            con1.andThen(con2).accept(s);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行结果将会首先打印完全大写的HELLO,然后打印完全小写的hello。

    andThen原理分析图解:

    在这里插入图片描述

    注意:
    	1.con1调用andThen方法,传递参数con2,所以anThen方法内部的this就是con1,after就是con2
    	2.andThen方法内部调用accept方法,前面隐藏了一个this,this代表调用andThen方法的对象,就是con1
    	3.andThen方法内部的t是谁?就是最后调用方法accept传递的s
    		this.accept(t) <==> con1.accept(s)   ①
    	4.con1调用andThen方法时传递的参数是con2,所以andThen方法内部的after就是con2
    		after.accept(t) <==> con2.accept(s)  ②
        5.通过分析,我们发现①和②中的内容,就是之前不用andThen方法,自己进行调用的过程
        6.以上分析,仍然是按照面向对象中方法调用的思路展开的,但实质上,我们要注意,Consumer接口中的andThen方法,返回的是一个Consumer,里面采用的是lambda表达式,其实是在做函数模型的拼接,把两个函数模型con1和con2拼接出一个新的模型,返回新的模型。所以con1.andThen(con2)是把con1和con2拼接成一个新的Consumer,返回的是lambda表达式的形式
        最后调用accept(s)方法时,其实执行的是lambda表达式{}中的代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. Function接口

    java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为“函数Function”。

    3.1.抽象方法:apply

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

    代码示例:

    String类型转换为Integer类型。

    /*
        java.util.function.Function: 转换型接口
            泛型T: 转换前的类型
            泛型R: 转换后的类型
    
            抽象方法:
                R apply(T t): 根据类型T的参数获取类型R的结果 把参数t转换成R类型的结果
    
            "123" --> 123
    
            需求:
                给你一个String类型的数字,给我转换成int数字
                分析:
                    用Function接口
                        T: 转换前的类型, String
                        R: 转换后的类型, Integer
     */
    public class Demo07Function {
        public static void main(String[] args) {
            String s = "123";
            //lambda标准格式
            fun(s,(String str)->{return Integer.parseInt(str);});
        }
        /*
            定义方法,使用Function接口作为参数
         */
        public static void fun(String s,Function<String,Integer> function) {
            Integer num = function.apply(s);
            System.out.println(num);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3.2.默认方法:andThen

    Function接口中有一个默认的andThen方法,用来进行组合操作。JDK源代码如:

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

    该方法同样用于“先做什么,再做什么”的场景,和Consumer中的andThen差不多:

    代码示例:

    将String的数字,转成int数字,再把int数字扩大10倍

    /*
        java.util.function.Function: 转换型接口
            泛型T: 转换前的类型
            泛型R: 转换后的类型
    
            默认方法:
                default  Function andThen(Function after):
                    先转换一次,再转换一次,一个挨着一个
    
            "123" --> 123 --> 1230 (扩大了10倍)
    
            需求:
                给你一个String类型的数字,
                    给我转先换成int数字
                    再给我把int数字扩大10倍
                分析:
                    用2个Function接口
                        第一个Function接口
                            T: 转换前的类型, String
                            R: 转换后的类型, Integer
                        第二个Function接口:
                            T: 转换前的类型, Integer
                            R: 转换后的类型, Integer
     */
    public class Demo08Function {
        public static void main(String[] args) {
            String s = "123";
            //lambda标准格式
            fun(s,(String str)->{
                return Integer.parseInt(str);
            },(Integer num) -> {
                return num*10;
            });        
        }
        /*
            定义一个方法,有两个Function接口作为参数
         */
        public static void fun(String s,Function<String,Integer> fun1,Function<Integer,Integer> fun2) {
            //1.先转换一次
            Integer num1 = fun1.apply(s);
            //2.再转换一次
            Integer num2 = fun2.apply(num1);
            System.out.println(num2);        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过andThen按照前后顺序组合到了一起。运行结果将会打印1230。但是我们却没有使用andThen方法,其实我上面的写法,就是andThen底层的代码实现。

    请注意,Function的前置条件泛型和后置条件泛型可以相同。

    为了方便大家理解,下面我们使用andThen方法进行演示

    public class Demo09Function {
        public static void main(String[] args) {
            String s = "123";
            //lambda标准格式
            fun(s,(String str)->{
                return Integer.parseInt(str);
            },(Integer num) -> {
                return num*10;
            });
            
        }
        /*
            定义一个方法,有两个Function接口作为参数
         */
        public static void fun(String s,Function fun1,Function fun2) {
           Integer num3 = fun1.andThen(fun2).apply(s);
            System.out.println(num3);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果仍然是1230。

    andThen原理分析图解:
    在这里插入图片描述

    注意:
    	1.fun1调用andThen方法传递参数fun2,所以andThen方法内部的this就是fun1,after就是fun2
    	2.andThen方法内部直接调用apply方法,前面隐藏了一个this,this代表调用andThen方法的对象,就是fun1
    	3.andThen方法内部的t是谁?就是最后调用方法apply传递的s
    		this.apply(t) <==> Integer num1 = fun1.apply(s) ①
    	4.fun1调用andThen方法时传递的参数是fun2,所以andThen方法内部的after就是fun2
    		after.apply(this.apply(t)) <==> Integer num2 = fun2.apply(num1)  ②
        5.通过分析,我们发现①和②中的内容,就是之前不用andThen方法,我们自己进行调用的过程
        6.以上分析,仍然是按照面向对象中方法调用的思路展开的,但实质上,我们要注意,Function接口中的andThen方法,返回的是一个Function,里面采用的是lambda表达式,其实是在做函数模型的拼接,把两个函数模型fun1和fun2拼接出一个新的模型,返回新的模型。所以fun1.andThen(fun2)是把fun1和fun2拼接成一个新的Function,返回的是lambda表达式的形式
        最后调用accept(s)方法时,其实执行的是lambda表达式{}中的代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4. Predicate接口

    有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate接口。

    4.1.抽象方法:test

    Predicate接口中包含一个抽象方法:boolean test(T t)。用于条件判断的场景:

    //1.练习:判断字符串长度是否大于5
    //2.练习:判断字符串是否包含"H"
    public class Demo10Predicate {
        public static void main(String[] args) {
            String str = "helloWorld";
            //1.练习:判断字符串长度是否大于5
            //lambda标准格式
            fun(str,(String s)->{return s.length()>5;});
            System.out.println("-----------------");
    
            //2.练习:判断字符串是否包含"H"
            //lambda标准格式
            fun(str,(String s)->{return s.contains("H");});
           
        }
        /*
            定义一个方法,参数是Predicate接口
         */
        public static void fun(String s,Predicate<String>predicate) {
            boolean result = predicate.test(s);
            System.out.println(result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。

    4.2.默认方法:and

    既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and。其JDK源码为:

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    
    • 1
    • 2
    • 3
    • 4

    代码示例:

    判断一个字符串既要包含大写“H”,又要包含大写“W”

    public class Demo11Predicate {
        public static void main(String[] args) {
            String str = "HelloWorld";
            //1.练习:判断一个字符串既要包含大写“H”,又要包含大写“W”
            //lambda标准格式
            fun1(str,(String s)->{return s.contains("H");},(String s)->{return s.contains("W");});
            System.out.println("------------");
            fun2(str,(String s)->{return s.contains("H");},(String s)->{return s.contains("W");});
        }
    
        /*
            演示and方法
            需要两个Predicate作为参数
            fun1方法没有使用and方法,就是p1和p2分别调用test方法,
            然后把结果进行&&运算--其实这是and方法的底层实现
         */
        public static void fun(String s,Predicate<String> p1,Predicate<String> p2) {
            //先判断一次
            boolean result1 = p1.test(s);
            //再判断一次
            boolean result2 = p2.test(s);
            //进行&&运算
            boolean result = result1&&result2;
            System.out.println(result);       
        }
        /*
            演示and方法
            需要两个Predicate作为参数
            
         */
        public static void fun2(String s,Predicate<String> p1,Predicate<String> p2) {
            boolean result = p1.and(p2).test(s);
            System.out.println(result); 
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    4.3.默认方法:or

    and的“与”类似,默认方法or实现逻辑关系中的“”。JDK源码为:

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    
    • 1
    • 2
    • 3
    • 4

    代码示例:

    字符串包含大写H或者包含大写W”

    public class Demo12Predicate {
        public static void main(String[] args) {
            String str = "Helloworld";
            //1.练习:判断一个字符串包含大写“H”或者包含大写“W”
            //lambda标准格式
            fun1(str,(String s)->{return s.contains("H");},(String s)->{return s.contains("W");});
            System.out.println("------------");
            fun2(str,(String s)->{return s.contains("H");},(String s)->{return s.contains("W");});
        }
    
        /*
            演示or方法
            需要两个Predicate作为参数
            fun1方法没有使用or方法,就是p1和p2分别调用test方法,
            然后把结果进行||运算--其实这是or方法的底层实现
         */
        public static void fun(String s,Predicate<String> p1,Predicate<String> p2) {
            //先判断一次
            boolean result1 = p1.test(s);
            //再判断一次
            boolean result2 = p2.test(s);
            //进行||运算
            boolean result = result1||result2;
            System.out.println(result);       
        }
        /*
            演示or方法
            需要两个Predicate作为参数
            
         */
        public static void fun2(String s,Predicate<String> p1,Predicate<String> p2) {
            boolean result = p1.or(p2).test(s);
            System.out.println(result); 
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    关于and和or方法的原理,可以参考andThen方法原理

    4.4.默认方法:negate

    “与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法negate的JDK源代码为:

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    
    • 1
    • 2
    • 3

    从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在test方法调用之前调用negate方法,正如andor方法一样:

    import java.util.function.Predicate;
    
    public class Demo13Predicate {
        private static void method(Predicate<String> predicate,String str) {
            boolean veryLong = predicate.negate().test(str);
            System.out.println("字符串很长吗:" + veryLong);
        }
    
        public static void main(String[] args) {
          	method(s -> s.length() < 5, "Helloworld");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    五、总结

    本文通过具体的例子,演示了函数式接口的定义和使用。以及常用的函数式接口。并给出了相关的练习题目。对于部分函数式接口中的默认方法,进行了图解分析,让你更加深刻的理解函数式接口的思想和目的。在以后实际的编程过程中,对于集合的操作,可以通过Stream流完成,而Stream流中的很多方法的参数都是函数式接口,通过本文的学习,你已经掌握了函数式接口的使用,相信后面学习Stream流是非常容易的。

  • 相关阅读:
    【问一问】消息队列
    基于STM32设计的便携式心电信号监测系统
    前后端分离
    taro 兼容支付宝小程序和微信小程序<七>-- 上传图片及图片转base64
    力扣之移除元素
    【笑小枫玩转SpringBoot系列】目录,一篇拥有一个系列,值得收藏哟~
    Android编写一个视频监控App
    算法笔记-第五章-大整数运算
    设计模式六大原则
    ApiFox添加全局参数
  • 原文地址:https://blog.csdn.net/cz_00001/article/details/126177193