1、函数式接口

1.1 函数式接口概述
函数式接口
  有且只有一个抽象方法的接口,可以有其他方法,静态、默认、私有。。。
  Java中的函数式编程体现就是lambda,所以函数式接口就是适用于lambda使用的接口,只有确保有且只有一个抽象方法,lambda才能顺利地进行推导
  语法糖是使用更方便但原理不变的代码语法,增强for就是迭代器的语法糖
  在应用层面上,lambda可以看作是匿名内部类的语法糖,但二者原理不同
    用匿名内部类会生成对应的class文件,但lambda没有,所以lambda效率更高
  使用注解@FunctionalInterface,可以检测接口是不是一个函数式接口,是则编译成功,否则编译失败(没有抽象方法或多于一个)
  使用:作为方法的参数和返回值类型

public class Demo01FunctionalInterface {
    public static void main(String[] args) {
        //调用函数式接口作为参数的方法,参数传递其实现类对象
        show(new MyFunctionalInterfaceImpl());
        //调用函数式接口作为参数的方法,参数传递其匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });
        //调用函数式接口作为参数的方法,参数使用lambda
        show(() -> {
            System.out.println("使用lambda重写接口中的抽象方法");
        });
        //简化lambda
        show(() -> System.out.println("简化后的lambda"));
    }
    public static void show(MyFunctionalInterface myinterface) {
        myinterface.method();
    }
}

1.2 存在性能浪费的日志案例
  日志可以帮助我们快速定位问题,记录程序执行过程中的情况,以便项目的监控和优化
  以下代码存在性能浪费问题,调用log方法,参数传递拼接后的字符串,会先进行字符串拼接再调用方法,如果等级不为1就白拼接了

public class Demo02Logger {
    private static void log(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
        }
    }
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1, msgA + msgB + msgC);
    }
}

1.3 使用lambda优化的日志案例
  lambda特点:延迟加载
  lambda使用前提:必须存在函数式接口
  使用lambda表达式作为参数传递,仅仅是将参数传递到show方法中,只有满足等级为1的条件,才会调用MergeMessage接口中的merge方法进行字符串拼接

//拼接字符串的函数式接口
@FunctionalInterface
public interface MergeMessage {
    //拼接字符串的方法
    public abstract String merge();
}
public class Demo03LoggerImprove {
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        show(1,() -> {
            System.out.println("满足条件执行该方法体");
            return msgA + msgB + msgC;
        });
        show(2,() -> {
            System.out.println("不满足条件不执行该方法体");
            return msgA + msgB + msgC;
        });
        //简化后
        show(1,() -> msgA + msgB + msgC);
    }
    //定义显示日志的方法,参数传递日志等级和定义的字符串拼接的函数式接口
    private static void show(int level, MergeMessage m) {
        if (level == 1) {
            //打印拼接好的字符串
            System.out.println(m.merge());
        }
    }
}

1.4 函数式接口作为方法参数
  使用函数式接口Runnable作为方法参数,就可以使用lambda表达式进行传参,与Thread的构造方法参数为Runnable的情况没有区别

public class Demo04FunctionalInterfaceAsPara {
    public static void main(String[] args) {
        //使用匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程" + Thread.currentThread().getName() + "启动了");
            }
        });
        startThread(() -> {
            System.out.println("线程" + Thread.currentThread().getName() + "启动了");
        });
        //lambda简化后
        startThread(() -> System.out.println("线程" + Thread.currentThread().getName() + "启动了"));
    }
    public static void startThread(Runnable r) {
        new Thread(r).start();
    }
}

1.5 函数式接口作为方法返回值
  函数式接口作为方法返回值类型,可以直接返回一个lambda表达式
  本例使用比较器Comparator接口作为方法返回值类型

public class Demo05FunctionalInterfaceAsReturn {
    public static void main(String[] args) {
        String[] arr = {"aaa", "o", "dd"};
        System.out.println(Arrays.toString(arr));//[aaa, o, dd]
        Arrays.sort(arr, getComparator());
        System.out.println(Arrays.toString(arr));//[aaa, dd, o]
    }
    public static Comparator<String> getComparator() {
        //返回匿名内部类
//        return new Comparator<String>() {
//            @Override
//            public int compare(String o1, String o2) {
//                //按照字符串长度降序排序,后-前
//                return o2.length() - o1.length();
//            }
//        };
        //返回lambda表达式
//        return (o1, o2) -> {
//            return o2.length() - o1.length();
//        };
        //优化后
        return (o1, o2) -> o2.length() - o1.length();
    }
}

1.6 自定义函数式接口

//自定义的函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
    public abstract void method();
    //void method2();//有一个以上的抽象方法或没有抽象方法就会注解编译报错
}
//接口实现类
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
    @Override
    public void method() {
        System.out.println("实现类重写接口中的抽象方法");
    }
}

2、常见的函数式接口

都在java.util.function包中
2.1 Supplier
Supplier接口
  方法:T get();接口指定什么类型的泛型,就返回什么类型的数据
  生产型接口 接口指定什么类型的泛型,接口中的get方法就会生产什么类型的数据

public class Demo01Supplier {
    //定义一个方法,参数传递Supplier<T>接口
    public static String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        //使用匿名内部类
        String s1 = getString(new Supplier<String>() {
                        @Override
                        public String get() {
                            //生产一个字符串并返回
                            return "匿名内部类";
                        }
                    });
        System.out.println(s1);
        //使用lambda表达式
        String s2 = getString(() -> {
                        return "lambda";
                    });
        System.out.println(s2);
        //优化后的lambda表达式 省略return 分号 大括号
        String s3 = getString(() -> "优化后的lambda表达式");
        System.out.println(s3);
    }
}

2.2 Supplier练习:求数组最大值
使用Supplier接口作为方法参数类型,通过lambda表达式求出int数组元素最大值
提示:接口泛型为Integer

public class Demo02SupplierMax {
    //定义方法,参数传递Supplier接口,泛型为Integer
    public static int max(Supplier<Integer> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        //定义int数组
        int[] array = {23, 54, 3, 22, 6};
        //调用max函数,间接调用Supplier接口的get函数
        int max = max(() -> {
            int m = array[0];
            for (int i = 1; i < array.length; i++) {
                if (m < array[i]) {
                    m = array[i];
                }
            }
            return m;
        });
        System.out.println(max);//54
    }
}

2.3 Consumer
Consumer接口
  方法:
    void accept(T t);消费一个指定泛型的数据
    Consumer andThen(Consumer<? super T> after);将两个Consumer接口连接在一起,再消费数据
    Consumer con1;
    Consumer con2;
    con1.andThen(con2).accept(t);谁在前面谁先消费
  消费型接口 与Supplier接口相反 用于消费数据 数据类型由泛型决定

public class Demo03Consumer {
    //accept方法
    //定义方法,参数传递字符串的姓名和Consumer接口,泛型使用String类型,可以使用Consumer接口消费字符串的姓名
    public static void method(String name, Consumer<String> consumer) {
        consumer.accept(name);
    }
    //andThen方法
    //定义方法,参数传递字符串和两个Consumer接口
    public static void method2(String name, Consumer<String> con1, Consumer<String> con2) {
        con2.andThen(con1).accept(name);//先执行con1的accept再执行con2的accept
    }
    public static void main(String[] args) {
        String s = "lambda";
        method(s, (String name) -> {
            //消费方式:直接输出
            System.out.println(name);//lambda
            //消费方式:反转输出
            String nameReverse = new StringBuilder(name).reverse().toString();
            System.out.println(nameReverse);//adbmal
        });
        method2(s, (String name) -> {
            System.out.println(name.toUpperCase());//LAMBDA
        }, (String name) -> {
            System.out.println(name.toLowerCase());//lambda
        });
    }
}

2.4 Consumer练习:格式化打印信息
按照格式"姓名:**。性别:。"打印信息

public class Demo04ConsumerPrint {
    //定义方法,参数传递字符串数组和两个Consumer接口
    public static void method(String[] array, Consumer<String> con1, Consumer<String> con2) {
        for (String s : array) {
            con1.andThen(con2).accept(s);
        }
    }

    public static void main(String[] args) {
        String[] people = {"张三,男", "李四,女", "王五,男"};
        method(people, (String s) -> {
            String name = s.split(",")[0];
            System.out.print("姓名:" + name + "。");
        }, (String s) -> {
            String sex = s.split(",")[1];
            System.out.println("性别:" + sex + "。");//姓名:张三。性别:男。
                                                    //姓名:李四。性别:女。
                                                    //姓名:王五。性别:男。
        });
    }
}

2.5 Predicate
Predicate接口
  方法:boolean test(T t);对某种数据类型的值进行判断
     Predicate and(Predicate<? super T> other);连接两个判断条件,表示并且关系

public class Demo05Predicate {
    //test方法
    //定义方法,参数传递字符串和Predicate接口,使用接口中的test方法对字符串进行判断,并将判断结果返回
    public static boolean method(String s, Predicate<String> predicate) {
        return predicate.test(s);
    }
    //and方法
    //判断字符串长度是否大于5且包含a
    public static boolean method2(String s, Predicate<String> pre1, Predicate<String> pre2) {
        return pre1.and(pre2).test(s);
    }
    //or方法
    //判断字符串长度是否大于5或包含a
    public static boolean method3(String s, Predicate<String> pre1, Predicate<String> pre2) {
        return pre1.or(pre2).test(s);
    }
    //negate方法
    //判断字符串长度是否大于5
    public static boolean method4(String s, Predicate<String> pre) {
        return pre.negate().test(s);
    }

    public static void main(String[] args) {
        //test方法
//        String s = "用于判断";
//        boolean b = method(s, (String str) -> {
//            if (str.length() > 5) {
//                return true;
//            } else {
//                return false;
//            }
//        });
//        System.out.println(b);//false
        //and方法
//        String s2 = "dbfhgs";
//        boolean b2 = method2(s2, (String str) -> {
//            if (s2.length() > 5) {
//                return true;
//            } else {
//                return false;
//            }
//        }, (String str) -> {
//            if (s2.contains("a")) {
//                return true;
//            } else {
//                return false;
//            }
//        });
//        System.out.println(b2);
        //or方法
//        String s3 = "afcs";
//        boolean b3 = method3(s3, (String str) -> {
//            if (s3.length() > 5) {
//                return true;
//            } else {
//                return false;
//            }
//        }, (String str) -> {
//            if (s3.contains("a")) {
//                return true;
//            } else {
//                return false;
//            }
//        });
//        System.out.println(b3);
        //negate方法
        String s4 = "fgh";
        boolean b4 = method4(s4, (String str) -> {
            if (s4.length() > 5) {
                return true;
            } else {
                return false;
            }
        });
        System.out.println(b4);//大于5true,取反false;小于5false,取反true
    }

}

2.6 Predicate练习:将字符串数组中符合条件的信息筛选到ArrayList集合中
条件:必须为女生且名字为两个字

public class Demo06PredicateSelect {
    public static void select(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            if (pre1.and(pre2).test(s)) {
                list.add(s);
            }
        }
        System.out.println(list);
    }

    public static void main(String[] args) {
        String[] arr = {"张三,女", "李四百,女", "王五,男"};
        select(arr, (String str) -> {
            String name = str.split(",")[0];
            if (name.length() == 2) {
                return true;
            } else {
                return false;
            }
        }, (String str) -> {
            String sex = str.split(",")[1];
            if (sex.equals("女")) {
                return true;
            } else {
                return false;
            }
        });
    }
}

2.7 Function
Function<T,R>接口
  方法:R apply(T t);根据类型T的参数获取类型R的结果
     Function<T,V> andThen(Function<? super R, ? extends V> after);

public class Demo07Function {
    //apply方法,将字符串类型的整数转换为Integer类型的整数
    public  static void method(String num, Function<String, Integer> fun) {
        int n = fun.apply(num);
        System.out.println(n);//123
    }
    //andThen方法,将字符串类型的数据转换为整型,加上10,再转换为字符串
    public static void method2(String num, Function<String, Integer> fun1, Function<Integer, String> fun2) {
        String s = fun1.andThen(fun2).apply(num);
        System.out.println(s);//149
    }
    public static void main(String[] args) {
//        String s = "123";
//        method(s, (String str) -> {
//            return Integer.parseInt(str);
//        });
        String s = "139";
        method2(s, (String str) -> {
            //将字符串转换为整数
            return Integer.parseInt(str) + 10;
        }, (Integer i) -> {
            //将整数转换为字符串
            return i + "";
        });
        //优化后
        method2(s, str -> Integer.parseInt(str) + 10, i -> i + "");
    }
}

2.8 Function练习:将字符串中的年龄部分截取出来String->String,转换为整型String->Integer,再加10Integer->Integer

public class Demo08FunctionPra {
    public static void method(String s, Function<String, String> fun1,Function<String, Integer> fun2, Function<Integer, Integer> fun3) {
        int num = fun1.andThen(fun2).andThen(fun3).apply(s);
        System.out.println(num);//28
    }

    public static void main(String[] args) {
        String s = "张三,18";
        method(s, (String str) -> {
           return str.split(",")[1];
        }, (String str) -> {
            return Integer.parseInt(str);
        }, (Integer i) -> {
            return i + 10;
        });
    }
}