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);
    
      }
    
  • 相关阅读:
    Array.sort源码
    Linkedlist源码
    最大公约数 2.7
    腾讯笔试题
    腾讯2014校园招聘笔试题
    指针问题
    JavaScript 日历
    QT 初阶 第二章 创建对话框(查找对话框实例)
    QT 初阶 1.3 节 控件的几何排列
    “项目中的问题”
  • 原文地址:https://www.cnblogs.com/lpl666/p/12015136.html
Copyright © 2011-2022 走看看