1.函数式接口

1.1 概念

      函数式接口在java中是指只有一个抽象方法的接口。

      函数式接口,就是适用于函数式编程场景的接口。在java中函数式编程就体现在Lambda,因此函数式接口就是能够适用于lambda使用的接口。只有确保接口中有且仅有一个抽象方法,lambda才能进行顺利的推导。

1.2 格式

修饰符 interface 接口名称 {
     public abstract 返回值类型 方法名称(可选参数信息);
      // 其他非抽象方法内容
}

       因为接口中抽象方法的修饰符可以省略。例;

public interface MyFunctionalInterface {
           void myMethod();
}

  1.3 @FunctionalInterface注解

      此注解和@override注解的作用类似。该住处应用于函数式接口的定义上。

@FunctionalInterface
public interface MyFunctionalInterface {
      void myMethod();
}

    一旦使用了该注解来定义函数式接口,编译器就会检查该接口是否是有且仅有一个抽象方法。

1.4 自定义函数式接口

    将函数式接口作为方法的参数。

public class Demo {
    private static void dos(FunctionInterface fi){
        fi.method();
    }

    public static void main(String[] args) {
        Demo.dos(()->{System.out.println("lambda表达式");});
    }
}

  

2.函数式编程

2.1 lambda的延迟执行

   有些代码执行后,结果不一定会被使用,进而就是造成资源的浪费,lambda表达式是延迟执行的,这就可以可以解决这种资源浪费的问题。

  性能浪费的日志案例:

     日志可以帮助我们快速的解决定位的问题,记录长须过程中的情况,以便瞠目的监控和优化。

 一种典型的场景的就是对参数进行有条件的使用。比如在对日志信息进行拼接之后,在满足条件的情况下进行打印输出:

public class DemoLogger {
    public static void log(int level,String msg){
        if(level==1){
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        String m="你好";
        String s="日志";
        String g="吗?";
        log(1,m+s+g);
    }
}

   这段代码就存在资源浪费的问题,因为不管level是够满足要就,系统会都会把这三个字符串拼接起来,作为参数,传到方法中。

   lambda表达式的更优写法

     lambda表达式的使用前提必须要有函数式接口。

public class DemoLogger1 {
    private static void log(int level,Message msg){
        if(level==1){
            System.out.println(msg.pinjie());
        }
    }

    public static void main(String[] args) {
        String m="你好";
        String s="日志";
        String g="吗?";
        log(1,()->{return m+s+g;});
    }
}

     这个代码只有在level满足要求时,才会对三个字符串进行拼接。

验证Lambda表达式的延迟性

public class DemoLogger1 {
    private static void log(int level,Message msg){
        if(level==1){
            System.out.println(msg.pinjie());
        }
    }

    public static void main(String[] args) {
        String m="你好";
        String s="日志";
        String g="吗?";
        log(2,()->{
            System.out.println("lambda表达式执行了");
            return m+s+g;});
    }
}

   从上述代码可以看出,level不满足条件,lambda表达式没有执行。

2.2 lambda表达式作为参数和返回值

   lambda表达式的原理实际上就是匿名对象。如果方法的参数是一个函数接口,那就该函数接口就可以被lambda表示替代。

  例如:java.lang.Runnable接口就是一个函数式接口。现在有一个方法使用该接口作为参数,那么就可以使用lambda表示进行提到。

public class DemoRunnable {
    private static void startThread(Runnable runnable){
        new Thread(runnable).start();
    }

    public static void main(String[] args) {
        startThread(()->{
            System.out.println("线程执行了!");
        });
    }
}

 如果一个方法的返回值类型是函数式接口,那么就可以直接返回一个lambda表达式。

   例如,需要一个方法来获取java.util.Comparator接口类型的对象作为排序器时候,就可以调用该方法获取。

public class DemoComparator {
    private static Comparator<String> newComparator(){
        return (a,b)->{return a.length()-b.length();};
    }

    public static void main(String[] args) {
        String[] arr={"abc","acvd","a"};
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr,newComparator());
        System.out.println(Arrays.toString(arr));
    }
}

3.常用函数式接口

3.1 Supplier接口

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

public class DemoSupplier {
    private static String getstring(Supplier<String> supplier){
        return supplier.get();
    }
    public static void main(String[] args) {
        String msg="你好";
        String getstring = getstring(() -> { return msg; });
        System.out.println(getstring);
    }
}

3.2 练习:求数组元素的最大值

      使用Supplier接口作为方法的参数,通过lambda表达式求出int数组中的最大值。

public class DemoSupplier {
    private static int getmax(Supplier<Integer> supplier){
        return supplier.get();
    }
    public static void main(String[] args) {
        int[] arr={1,3,4,6,9};
       Integer getmax = getmax(() -> {
           int max=arr[0];
           for (int i = 0; i < arr.length; i++) {
               if(max>arr[i]){
                   max=max;
               }else{
                   max=arr[i];
               }
           }
           return max;
        });
        System.out.println(getmax);
    }
}

3.3 Consumer接口

      java.util.function.Consumer<T>接口正好与Supplier接口相反,他不是生产一个数据,而是消费一个数据,其数据类型根据泛型决定。

  抽象方法:accept

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

public class Democonsume {
    private static void consunmerString(Consumer<String> f){
        f.accept("你好");
    }

    public static void main(String[] args) {
        consunmerString((s)->{System.out.println(s);});
    }
}

    默认方法:andThen

    如果一个方法的参数和返回值都是Consumer类型。 那么在消费数据时,会做一个操作,然后在做一个操作,实现组合。这个方法就是Consumer接口中的default方法andThen。

   要实现组合,就需要两个或者多个lambda表达式,andThen的语义就是一步一步的执行;

public class DemoandThen {
    private static void consumerstring(Consumer<String> c1,Consumer<String> c2){
        c1.andThen(c2).accept("nihao");
    }

    public static void main(String[] args) {
        consumerstring((s)-> System.out.println(s.toUpperCase()),
                        s -> System.out.println(s.toLowerCase()));
    }
}

     运行结果是先输出大写的nihao,然后打印小写的nihao。

3.4 格式化打印

        下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个Consumer 接口的Lambda实例,将打印性别的动作作为第二个Consumer 接口的Lambda实例,将两个Consumer 接口按照顺序“拼接”到一起。

public class DemoandThen {
    private static void consumerstring(Consumer<String> c1,Consumer<String> c2,String[] arr){
        for (String s : arr) {
            c1.andThen(c2).accept(s);
        }

    }

    public static void main(String[] args) {
        String[] arr={"路飞,男","娜美,女","索隆,男","乔巴,动物"};
        consumerstring(s -> System.out.print(s.split(",")[0]+"性别:"),
                       s -> System.out.println(s.split(",")[1]),
                       arr);
    }
}
路飞性别:男
娜美性别:女
索隆性别:男
乔巴性别:动物

  3.5 Predicate接口

       java.util.function.Predicate<T> 接口,对某种类型的数据进行判断,从而得到一个boolean值结果。

 抽象方法:test

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

public class DemoPredicate {
    public static void predicate(Predicate<String> predicate){
        boolean test = predicate.test("我是字符串");
        System.out.println(test);
    }

    public static void main(String[] args) {
        predicate(s->s.length()>3);
    }
}
true

       条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于3则true.

默认方法:and

    predicate是条件判断,就会有三种逻辑关系,与、或、非。其中将两个Predicate 条件使用“与”逻辑连接起来实
现“并且”的效果时,可以使用default方法and 。

public class DemoPredicate {
    public static void predicate(Predicate<String> p1,Predicate<String> p2){
        boolean test = p1.and(p2).test("我是字符串");
        System.out.println(test);
    }

    public static void main(String[] args) {
        predicate(s->s.length()>3,
                  s->s.length()<6);
    }
}
true

     默认方法:or

                  与and方法同类型

public class DemoPredicate {
    public static void predicate(Predicate<String> p1,Predicate<String> p2){
        boolean test = p1.or(p2).test("我是字符串");
        System.out.println(test);
    }

    public static void main(String[] args) {
        predicate(s->s.length()>3,
                  s->s.length()>6);
    }
}
true

  默认方法:negate

       它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在test 方法调用之前
调用negate 方法,正如and 和or 方法一样:

public class DemoPredicate {
    public static void predicate(Predicate<String> p1){
        boolean test = p1.negate().test("我是字符串");
        System.out.println(test);
    }

    public static void main(String[] args) {
        predicate(s->s.length()>3
                  );
    }
}
false

   3.6 集合信息筛选

     数组当中有多条“姓名+性别”的信息如下,请通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:

                    1. 必须为女生;

                    2. 姓名为4;

public class DemoPredicate {
    public static ArrayList<String> predicate(Predicate<String> p1, Predicate<String> p2, String[] arr){
        ArrayList<String> a=new ArrayList<>();
        for (String s : arr) {
            if(p1.and(p2).test(s)){
                a.add(s);
            }
        }
        return a;
    }

    public static void main(String[] args) {
        String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
        ArrayList<String> pre = predicate(s -> "女".equals(s.split(",")[1]),
                s -> s.split(",")[0].length() == 4,
                array
        );
        System.out.println(pre);
    }
}
[迪丽热巴,女, 古力娜扎,女]

3.7 Function 接口

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

     抽象方法:apply

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

     例如:将String类型转换成Integer类型。

public class DemoFunction {
    public static void method(Function<String,Integer> f){
        Integer apply = f.apply("100");
        System.out.println(apply);
    }

    public static void main(String[] args) {
        method(s-> Integer.parseInt(s));
    }
}
100

  默认方法:andThen

      Function 接口中有一个默认的andThen 方法,用来进行组合操作,该方法同样用于“先做什么,再做什么”的场景,和Consumer 中的andThen 差不多:

public class DemoFunction {
    public static void method(Function<String,Integer> f1,Function<Integer,Integer> f2){
        Integer apply = f1.andThen(f2).apply("100");
        System.out.println(apply);
    }

    public static void main(String[] args) {
        method(s-> Integer.parseInt(s), s->s*=100);
    }
}
1000

    3.8 自定义函数模型拼接

       请使用Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:

              String str = "赵丽颖,20";

          1.将字符串截取数字年龄部分,得到字符串;

          2. 将上一步的字符串转换成为int类型的数字;

          3. 将上一步的int数字累加100,得到结果int数字。

public class DemoFunction {
    public static int method(Function<String,String> f1,
                             Function<String,Integer> f2,
                             Function<Integer,Integer> f3,String str){
        return f1.andThen(f2).andThen(f3).apply(str);
    }

    public static void main(String[] args) {
        String str="赵丽颖,20";
        int method = method(s ->s.split(",")[1],
                s -> Integer.parseInt(s),
                s -> s += 100,
                str
        );
        System.out.println(method);
    }
}
120