zoukankan      html  css  js  c++  java
  • JavaSE-函数式编程接口

    第一章:函数式接口

    1.1 函数式接口介绍

    ​ 函数式接口在Java中是指:有且仅有一个抽象方法的接口

    ​ 函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

    从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

    1.2 格式

    只要确保接口中有且仅有一个抽象方法即可:

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

    1.3 @FunctionalInterface注解

    与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:

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

    一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

    1.4 自定义函数式接口

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

      public  static  void show(MyFunctionalInterface func){
        func.mythond();
      }
      public static void main(String[] args) {
        // 传入匿名内部类
        show(new MyFunctionalInterface() {
          @Override
          public void mythond() {
            System.out.println("执行了");
          }
        });
        // 简写Lambda表达式
        show(()-> System.out.println("执行了"));
      }
    

    第二章:函数式编程

    ​ 在兼顾面向对象特性的基础上,Java语言通过Lambda表达式与方法引用等,为开发者打开了函数式编程的大门。 下面我们做一个初探。

    2.1 Lambda延迟执行

    ​ 有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

    性能浪费的日志案例

    注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。 一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:

    public class Demo01Logger {
        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);
        }
    }
    
    

    这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方 法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

    备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行 字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进 行字符串拼接。例如: LOGGER.debug("变量{}的取值为{}。", "os", "macOS") ,其中的大括号 {} 为占位 符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字 符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。

    体验Lambda的更优写法

    使用Lambda必然需要一个函数式接口:

    @FunctionalInterface
    public interface MessageBuilder {
      String message();
    }
    

    然后对log方法进行改造

    public class Test01 {
      private static  void  log(int level,MessageBuilder builder){
        if(level==1){
          String mes = builder.message();
          System.out.println(mes);
        }
      }
      public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1,()-> msgA+msgB  +msgC);
      }
    }
    

    这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接

    2.2 使用Lambda作为参数和返回值

    如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。

    如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。

    使用Lambda表达式作为方法参数,其实就是使用函数式 接口作为方法参数。 例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就 可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。

      public static void startThead(Runnable run){
        new Thread(run).start();
      }
      public static void main(String[] args) {
        startThead(()-> System.out.println(Thread.currentThread().getName()));
      }
    

    类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一 个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

      private static Comparator<String> newComparator() {
          return (o1, o2) -> o1.length()-o2.length();
      }
      public static void main(String[] args) {
        // 对字符串数组排序,按照字符长度排序
        String[]arr = {"ab","b","abc","abcde","abcd","aaaaaa"};
        //
        Arrays.sort(arr);
        Arrays.sort(arr,newComparator());
        System.out.println(Arrays.toString(arr)); // [b, ab, abc, abcd, abcde, aaaaaa]
      }
    

    其中直接return一个Lambda表达式即可。

    第三章:常用的函数式接口

    JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。 下面是最简单的几个接口及使用示例。

    3.1 Supplier接口

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

      public static String show(Supplier<String> sp){
        return sp.get();
      };
      public static void main(String[] args) {
        System.out.println(show(()->"你好Java"));
      }
    

    练习:求数组最大值

      public static int getMax(Supplier<Integer> sp){
        return sp.get();
      }
      public static void main(String[] args) {
        int[]arr={11,2,3,6,4,66,22,19};
        int max = getMax(()->{
          int maxValue = arr[0];
          for (int i = 1; i < arr.length; i++) {
            if(maxValue<arr[i]){
              maxValue = arr[i];
            }
          }
          return maxValue;
        });
        System.out.println(max);
      }
    

    3.2 Consumer接口

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

    • 抽象方法 :accept,Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:

        public static void printList(ArrayList<String> list, Consumer<ArrayList> con){
          con.accept(list);
        }
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("张三");
            list.add("李四");
            list.add("王五");
            printList(list,arrayList->{
              for (int i = 0; i < arrayList.size();i++){
                System.out.println("姓名:" + arrayList.get(i));
              }
            });
        }
      
    • 默认方法:如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:

      • 源码

        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) ‐> { accept(t); after.accept(t); };
        }
        /*
        备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出
        NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
        */
        
      • 代码

        //要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况:
          public static void printList(ArrayList<String> list, Consumer<ArrayList> one,Consumer<ArrayList> two){
            one.andThen(two).accept(list);
          }
          public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("张三");
            list.add("李四");
            list.add("王五");
            printList(list,arrayList->{
              System.out.println("======顺序打印=======");
              for (int i = 0; i < arrayList.size();i++){
                System.out.println("姓名:" + arrayList.get(i));
              }
            },arrayList -> {
              System.out.println("======倒序打印=======");
              for (int i = arrayList.size()-1; i >=0;i--){
                System.out.println("姓名:" + arrayList.get(i));
              }
            });
          }
        

    3.3 Predicate接口

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

    抽象方法test

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

      public static void main(String[] args) {
        // 判断一个字符串中是否包含大写的J和大写的H
        String str = "Hello Java";
        boolean isHas = checkStr(str,data->data.contains("J")&&data.contains("H"));
        System.out.println(str + "是否包含大写J和H:"+isHas);
      }
      public static boolean checkStr(String str , Predicate<String> predicate){
        return predicate.test(str);
      }
    

    默认方法and、or、nagate

    • 方法
    // and 且
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) ‐> test(t) && other.test(t);
    }
    // or 或
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) ‐> test(t) || other.test(t);
    }
    // nagate非
    default Predicate<T> negate() {
    	return (t) ‐> !test(t);
    }
    
    
    • 代码

      // and
        public static void main(String[] args) {
          // 判断一个字符串中是否包含大写的J和大写的H
          String str = "Hello Java";
          boolean isHas = checkStr(str,data->data.contains("J"),data->data.contains("H"));
          System.out.println(str + "包含大写J和H:"+isHas);
        }
        public static boolean checkStr(String str , Predicate<String> one,Predicate<String> two){
          return one.and(two).test(str);
        }
      // or
        public static void main(String[] args) {
          // 判断一个字符串中是否包含大写的J和大写的H
          String str = "Hello Java";
          boolean isHas = checkStr(str,data->data.contains("J"),data->data.contains("H"));
          System.out.println(str + "包含大写J或H:"+isHas);
        }
        public static boolean checkStr(String str , Predicate<String> one,Predicate<String> two){
          return one.or(two).test(str);
        }
      }
      // nagate
        public static void main(String[] args) {
          // 判断一个字符串中是否包含大写的J和大写的H
          String str = "Hello Java";
          boolean isHas = checkStr(str,data->data.contains("J")&&data.contains("H"));
          System.out.println(str + "没有包含大写J和H:"+isHas);
        }
        public static boolean checkStr(String str , Predicate<String> predicate){
          return predicate.negate().test(str);
        }
      }
      

    3.4 Function接口

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

    抽象方法:apply

    Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。 使用的场景例如:将 String 类型转换为 Integer 类型。

      public static void main(String[] args) {
        methond(str->{
          return Integer.parseInt(str);
        });
        // 打印结果30
      }
      public static void methond(Function<String,Integer> func){
        Integer num = func.apply("20");
        System.out.println(num + 10);
      }
    

    默认方法: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));
    }
    

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

      public static void main(String[] args) {
        // 将一个字符串变成数字后,再乘以10的结果
        test(str->Integer.parseInt(str),num->num * 10);
      }
      public static void test(Function<String,Integer> one,Function<Integer,Integer> two){
        int result = one.andThen(two).apply("20");
        System.out.println("结果是:" + result);
    
      }
    
  • 相关阅读:
    451. Sort Characters By Frequency
    424. Longest Repeating Character Replacement
    68. Text Justification
    44. Wildcard Matching
    160. Intersection of Two Linked Lists
    24. Swap Nodes in Pairs
    93. 递归实现组合型枚举
    98. 分形之城
    97. 约数之和
    96. 奇怪的汉诺塔
  • 原文地址:https://www.cnblogs.com/lpl666/p/12015136.html
Copyright © 2011-2022 走看看