JAVA知识回顾之Lambda


0x00 概述

本文转载

 

0x01 学习Lambda的理由

  1. 绝大多数公司代码的主流风格。
  2. 大数据量下处理集合效率高,优秀的高并发解决。
  3. 代码的可读性增强。
  4. 消灭嵌套地狱。>形状的if或者for再也不用写了。

为了了解Lambda表达式,我们必须了解什么是函数式接口,这是Lambda表达式得以实现的依据。

在java中,函数式接口指注解了@FunctionalInterface(非必须)的接口。
函数式接口具有一下特性:

  • 接口中有且仅有一个抽象方法(必须)。

除却以上性质与普通的接口没有区别。

由以上定义,那么问题来了,为什么要使用函数式接口?

让我们先看一个接口的实现案例。

  // 传统的接口实现方式
  interface MyInterface {
  void test();
  }
  class MyInterfaceImpl implements MyInterface{
  public void test() {
  // 各种处理
  }
  }
  class Client {
  public void process() {
  MyInterface mif = new MyInterfaceImpl();
  mif.test();
  }
  }

目前看上去我们的实现是没有什么问题的,无外乎定义一个接口,然后定义接口的实现类,在客户端调用的过程中父类引用子类对象。。。
但是,如果这么定义的话,意味着实现类是可以复用的。实际上应该反过来思考,就因为需要复用实现类,我们才这么定义接口与实现。

有基础的小伙伴肯定反应过来我想说的问题:如果一个接口的实现根据业务需求在项目中指调用两三次,并且每次的实现方式还不同,我需要为了这三个不一样的实现分别写三个不同的实现类吗?

其实没有必要,由此引出接下来的内容:匿名内部类实现接口。

  // 匿名内部类的接口实现方式
  interface MyInterface {
  void test();
  }
  class Client {
  public void process() {
  MyInterface mif = new MyInterface() {
  @Override
  public void test() {
  // 各种处理
  }
  };
  mif.test();
  }
  /*
  其实不难看出,对于只有一个抽象方法的接口,匿名内部类的实现方式比较冗余
  首先new MyInterface(){}并不是非写不可,
  @Override,public,void,test也同样。
  可能有同学看到这里已经蒙了:这人在说啥?我接下来解释。
  接口只有一个抽象方法也就是说没有重载,所以我们通过明确写出方法的参数列(类型也不需要,可推定)
  已经方法体就可以确定重写的是哪个方法,也就省去了@Override public void test。
  又因为变量声明了一个接口类型所以就知道需要实例化的对象接口也就省去了new MyInterface(){}
  至此代码变为了以下这种形式:
  MyInterface mif = () -> { /* 各种处理 */ };
  */
  }

虽然上述的实现方式可以让我们比较方便的实现需求频繁变动且复用需求低的接口,但人类是非常懒的,为了让我们实现上述功能的同时写更少的代码,技术人员发明了lambda[1]表达式。

接下来,我们来看看上述实现使用lambda的效果。

  // lambda的接口实现方式
  interface MyInterface {
  void test();
  }
  class Client {
  public void process() {
  MyInterface mif = () -> { /* 各种处理 */ };
  mif.test();
  }
  }

代码量肉眼可见的减少,接下来解释为什么可以这么写。

  MyInterface mif = () -> { /* 各种处理 */ };
  • ():接口抽象方法的形式参数。
  • ->:箭头标记,固定写法。
  • {}:实现的方法体。

lambda的省略规则

  // 方法参数类型可以省略
  (a, b) -> { /* 各种处理 */ };
  // 方法体只有一行代码时,{}、;、return可省略(必须同时省略)
  (a, b) -> /* 各种处理 */;
  // 方法参数只有一个时,()可以省略
  a -> /* 各种处理 */;
  // 方法无参时,()不可省略
  () -> /* 各种处理 */;

仔细观察上述的所有写法可以了解到lambda表达式与传统匿名内部类的实现方式有一个本质的区别:有且仅有一个实现的抽象方法。这也是为什么函数式接口只能定义一个抽象方法的原因。

补充

  1. lambda基本可以认为是匿名内部类的语法糖[2](不太准确)
    但lambda与匿名内部类在原理上有一个区别:

    • 匿名内部类会生成class文件

    • lambda不会生成class文件

  2. lambda有着延迟执行的特点

      public Main {
      public static void main(String args[]) {
      String str1 = “你”;
      String str2 = “是”;
      String str3 = “谁?”;
      // lambda的实现方式
      Test.doTest(true, () -> {
      System.out.println(“满足条件时执行”);
      return “lambda实现:”+str1+str2+str3;
      });
      // 匿名内部类的实现方式(同样有延迟执行的特点)
      Test.doTest(false, new Test() {
      @Override
      public void test() {
      System.out.println(“满足条件时执行”);
      return “匿名内部类实现:”+str1+str2+str3;
      }
      });
      // 常规写法
      doTest(false, str1+str2+str3);
      /*
      满足条件时执行
      lambda实现:你是谁?
      */
      /*
      通常情况下会使用常规写法,因为理解简单,但存在一个问题,
      可以看出flag=false时,确实没有输出str,但str却提前拼接好,
      导致性能的浪费。
      为此使用lambda(推荐)或者匿名内部类的形式,将字符串的拼接
      延迟到判定条件之后。
      */
      }
      public static void doTest(boolean flag, String str) {
      if (flag) {
      System.out.println(str);
      }
      }
      }
      interface Test {
      String test();
      static void doTest(boolean flag, Test t) {
      if (flag) {
      System.out.println(t.test());
      }
      }
      }

0x02 核心函数式接口

Supplier

方法签名:T get()
作用:供应商接口。生成T类型的数据。

  public class Main {
  public static void main(String[] args) {
  int nums[] = {1, 23, 135, 534, 6245, 16254, 3547345};
  Integer res = test(() -> {
  int max = -1;
  for (int num : nums) {
  max = Math.max(max, num);
  }
  return max;
  });
  System.out.println(“最大值:” + res); // 最大值:3547345
  }
  public static <T> T test(Supplier<T> s) {
  return s.get();
  }
  }

Consumer

方法签名:void accept(T t)
作用:消费者接口。使用T类型的数据。

  // 格式化打印
  public class Main {
  public static void main(String[] args) {
  String strs[] = {“张三 男”, “李四 男”, “小红 女”};
  test(strs, (arr) -> {
  for (String str : arr) {
  String s[] = str.split(” “);
  System.out.println(s[0] + “:” + s[1]);
  }
  });
  /*
  张三:男
  李四:男
  小红:女
  */
  }
  public static <T> void test(T t, Consumer<T> c) {
  c.accept(t);
  }
  }

当想将上面的数据正向与逆向输出两遍怎么办?

  // 正向与逆向格式化打印, 使用andThen
  public class Main {
  public static void main(String[] args) {
  String strs[] = {“张三 男”, “李四 男”, “小红 女”};
  Consumer<String[]> c1 = (arr) -> {
  System.out.println(“——正序输出——“);
  for (String str : arr) {
  String s[] = str.split(” “);
  System.out.println(s[0] + “:” + s[1]);
  }
  };
  Consumer<String[]> c2 = (arr) -> {
  System.out.println(“——逆序输出——“);
  for (int i = arr.length – 1; i >= 0; i –) {
  String s[] = arr[i].split(” “);
  System.out.println(s[0] + “:” + s[1]);
  }
  };
  test(strs, c1.andThen(c2));
  /*
  ——正序输出——
  张三:男
  李四:男
  小红:女
  ——逆序输出——
  小红:女
  李四:男
  张三:男
  */
  }
  public static <T> void test(T t, Consumer<T> c) {
  c.accept(t);
  }
  }

由此看出两种写法等效,并且可以看出andThen可以链接两个Consumer的处理变为一个处理,或者说一起处理。当需要链接的Consumer数量不定时,有非常大的作用。传入的参数只需如下即可。

  // c1,c2,c3,c4,c5均为Consumer实例
  // 如此一来就可以连续执行c1,c2,c3,c4,c5的处理了
  test(strs, c1.andThen(c2).andThen(c3).andThen(c4).andThen(c5));

Predicate

方法签名:boolean test(T t)
作用:断言接口。封装判断语句。

其实Predicate就是将条件语句打包成一个类,减少编程时传参的麻烦,同时使得条件语句也可以延迟执行

  public class Main {
  public static void main(String[] args) {
  int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
  test(arr, (t) -> {
  if (t > 12523) return true;
  return false;
  });
  /*
  16254
  3547345
  */
  }
  public static void test(int arr[], Predicate<Integer> pre) {
  for (int s : arr) {
  // 用pre.test(s)来代替条件语句
  if (pre.test(s)) {
  System.out.println(s);
  }
  }
  }
  }

default方法

方法签名:Predicate<T> and(Predicate<? super T> other)
作用:返回当前断言 && 入参断言。

满足当前断言和入参断言时,返回true。

  public static void main(String[] args) {
  int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
  Predicate<Integer> pre1 = (t) -> {
  if (t > 12523) return true;
  return false;
  };
  Predicate<Integer> pre2 = (t) -> {
  if (t < 300000) return true;
  return false;
  };
  // 等价于 t > 12523 && t < 300000 的条件
  test(arr, pre1.and(pre2));
  /*
  16254
  */
  }
  public static void test(int arr[], Predicate<Integer> pre) {
  for (int s : arr) {
  if (pre.test(s)) {
  System.out.println(s);
  }
  }
  }

方法签名:Predicate<T> or(Predicate<? super T> other)
作用:返回当前断言 || 入参断言。

满足当前断言或者入参断言时,返回true。

  public static void main(String[] args) {
  int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
  Predicate<Integer> pre1 = (t) -> {
  if (t > 12523) return true;
  return false;
  };
  Predicate<Integer> pre2 = (t) -> {
  if (t < 300000) return true;
  return false;
  };
  // 等价于 t > 12523 && t < 300000 的条件
  test(arr, pre1.or(pre2));
  /*
  1
  23
  135
  534
  6245
  16254
  3547345
  */
  }
  public static void test(int arr[], Predicate<Integer> pre) {
  for (int s : arr) {
  if (pre.test(s)) {
  System.out.println(s);
  }
  }
  }

方法签名:Predicate<T> negate()
作用:返回当前断言的取反。

  public static void main(String[] args) {
  int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
  Predicate<Integer> pre1 = (t) -> {
  if (t < 12523) return true;
  return false;
  };
  Predicate<Integer> pre2 = (t) -> {
  if (t > 300000) return true;
  return false;
  };
  // 等价于 t > 12523 && t < 300000 的条件
  test(arr, pre1.or(pre2).negate());
  /*
  16254
  3547345
  */
  }
  public static void test(int arr[], Predicate<Integer> pre) {
  for (int s : arr) {
  if (pre.test(s)) {
  System.out.println(s);
  }
  }
  }

其他方法

  static <T> Predicate<T> isEqual(Object targetRef) {
  return null == targetRef ? Objects::isNull : (object) -> {
  return targetRef.equals(object);
  };
  }
   
  static <T> Predicate<T> not(Predicate<? super T> target) {
  Objects.requireNonNull(target);
  return target.negate();
  }
  • isEqual:传入一个对象,当对象引用null时,返回isNull方法引用的Predicate,否则返回equals的Predicate
  • not:返回目标Predicate的negate

Function

方法签名:R apply(T var1)
作用:将T类型转为R类型,返回。

  public class Main {
  public static void main(String[] args) {
  String str = “13523”;
  Integer i = test(str, (t) -> {
  return Integer.parseInt(str);
  });
  System.out.println(“i的value: ” + i); // i的value: 13523
  }
  public static <T, R> R test(T t, Function<T, R> f) {
  return f.apply(t);
  }
  }

default方法

方法签名:<V> Function<T, V> andThen(Function<? super R, ? extends V> after)
作用:将两个Function结合,返回。

先根据当前Function执行类型转换,然后再根据入参Function执行类型转换。

  public class Main {
  public static void main(String[] args) {
  Function<String, Integer> f1 = (t) -> {
  return Integer.parseInt(t);
  };
  Function<Integer, String> f2 = (t) -> {
  return String.valueOf(t);
  };
  String str = “13523”;
  String str1 = test(str, f1.andThen(f2));
  System.out.println(“str1的value: ” + str1); // str1的value: 13523
  /*
  String -> Integer -> String
  */
  }
  public static <T, R> R test(T t, Function<T, R> f) {
  return f.apply(t);
  }
  }

方法签名:<V> Function<V, R> compose(Function<? super V, ? extends T> before)
作用:将两个Function结合,返回。

与andThen刚好相反,先根据入参Function执行类型转换,然后再根据当前Function执行类型转换。

  public class Main {
  public static void main(String[] args) {
  Function<String, Integer> f1 = (t) -> {
  return Integer.parseInt(t);
  };
  Function<Integer, String> f2 = (t) -> {
  return String.valueOf(t);
  };
  Integer i = 13523;
  Integer i1 = test(i, f1.compose(f2));
  System.out.println(“i1的value: ” + i1);
  /*
  Integer -> String -> Integer
  */
  }
  public static <T, R> R test(T t, Function<T, R> f) {
  return f.apply(t);
  }
  }

其他方法

  static <T> Function<T, T> identity() {
  return (t) -> {
  return t;
  };
  }
  • identity:返回一个和输入类型相同的输出类型。

0x03 总结

Lambda的特点

  • 语法只关注参数与方法体
  • 可推导即可省略

Lambda的作用

  • 优化部分匿名内部类的写法。(函数式接口)

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/283141.html

(0)
上一篇 2022年8月30日
下一篇 2022年8月30日

相关推荐

发表回复

登录后才能评论