JavaSE-函数式编程接口


[TOC]

第一章:函数式接口

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);

  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352