Lambda演进

小王在公司正在开发一个学生管理系统,产品经理向他提出一个需求,要筛选出年龄大于15的学生,于是小王写出了以下代码:

    public static List<Student> filterAgeStudent(List<Student> students) {
        List<Student> list = Lists.newArrayList();
        for (Student student : students) {
            if (student.getAge() > 15) {
                list.add(student);
            }
        }
        return list;
    }

过了几天产品经理又提出了一个需求,要筛选出体重大于50KG的学生,于是小王新增了一个方法:

    public static List<Student> filterWeightStudent(List<Student> students) {
        List<Student> list = Lists.newArrayList();
        for (Student student : students) {
            if (student.getWeight() > 50) {
                list.add(student);
            }
        }
        return list;
    }

过了一段时间,产品提出了要筛选出体重大于50并且年龄要大于15的学生,小王突然感觉到这不是一个简单的需求,于是小王仔细思考了一下,突然想到将每种筛选的策略抽象成为一个接口,并且将这个接口当做一个参数传入方法中,这样每次就可以只新增策略,其他代码不需要更改了,这样就满足了软件设计的六大原则的开放闭合原则,于是乎诞生以下的设计和代码:

public interface StudentPredicate {
    boolean filter(Student student);
}
public class AgeStudentPredicate implements StudentPredicate {
    @Override
    public boolean filter(Student student) {
        return student.getAge() > 20 ? true : false;
    }
}
public static List<Student> filterStudent(List<Student> students,
                                              StudentPredicate predicate) {
    List<Student> list = Lists.newArrayList();
    for (Student student : students) {
       if (predicate.filter(student)) {
          list.add(student);
       }
    }
    return list;
}

经过一段时间的学习,小王接触到匿名类,于是小王代码进行更改,以后再也不需要写策略了:

List<Student> list = filterStudent(students, new StudentPredicate() {
     @Override
     public boolean filter(Student student) {
         return student.getAge() > 15;
     }
});

学习到匿名类以后,小王感觉到Java的浩瀚,然后继续学习,后来接触到Lambda,于是对待做了以下改造:

 List<Student> list = filterStudent(students, student -> student.getAge() > 15);

Lambda知识整理

Lambda定义

从上面的演进过程,我们基本上可以得到Lambda表达式是一种匿名函数,简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。Java中的Lambda表达式通常使用(argument) -> (body)语法书写,常用的Lamda表达式例子有:

(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }
函数式接口

函数式接口指的是是只包含一个抽象方法声明的接口。例如java.lang.Runnable就是一种函数式接口,在 Runnable接口中只声明了一个抽象方法方法void run();

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

对于注解@FunctionalInterface对于lamda表达式来说的话并不是必要的,@FunctionalInterface是Java8新加入的一种接口,用于指明该接口类型声明是根据Java语言规范定义的函数式接口。Java8还声明了一些Lambda表达式可以使用的函数式接口,当你注释的接口不是有效的函数式接口时,可以使用@FunctionalInterface解决编译层面的错误。

常用函数式

Java8中在java.util.function中引入了很多的函数式接口,这里介绍一下3个常用的函数式接口,

  1. Predicate
    Predicate接口定义一个名叫test的抽象方法,它接收泛型T对象,并返回一个boolean类型。经常使用到的地方是在流处理的过程中filter方法,满足条件的数据才会被过滤出来,例如我们上面的例子也可以改造成为Predicate函数式接口的形式。
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}
public static List<Student> filterStudent(List<Student> students,
                                          Predicate<Student> predicate) {
   List<Student> list = Lists.newArrayList();
   for (Student student : students) {
   if (predicate.test(student)) {
     list.add(student);
   }
  }
  return list;
}
  1. Consumer
    Consumer定义一个名叫accept的抽象方法,他接受泛型T的对象,没有返回值。如果你需要访问泛型对象T,并其进行修改,就使用Consumer。经常使用的地方就是常用的forEach方法。
@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
}
void forEachOrdered(Consumer<? super T> action);
  1. Function
    Function定义一个叫apply的方法,他接受一个泛型对象T,返回一个泛型对象R。如果你需要定义一个Lambda表达式,将输入的对象映射到输出,就使用Function,经常使用到的地方就是常用的map方法。
@FunctionalInterface
public interface Function<T, R> {
  R apply(T t);
}
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Lambda原理窥探

小王经过上面一系列学习,开始思考Lambda的原理是什么,因为Java8中每一个Lambda表达式必须有一个函数式接口与之对应,小王就思考经过编译器编译以后到可能实现的方式有两种,一种生成实现接口的类,另外一种是内部类,于是决定看一下反编译的以后代码,以解除心中的疑惑;

@FunctionalInterface
public interface Func {
    int add(int x, int y);
}
public class LambdaTest {
    public static void main(String[] args) {
        Func func = (x, y) -> x + y;
        System.out.println(func.add(1, 2));
    }
}

通过javap -p -v -c LambdaTest.class查看反编译后的代码,

Classfile /Users/wangtongzhou/Documents/Java/learning/target/classes/com/springboot2/learning/javabasic/java8/LambdaTest.class
  Last modified 2020-7-11; size 1392 bytes
  MD5 checksum ec7d77a8b0b0a0cb5940f80a9b27b3d0
  Compiled from "LambdaTest.java"
public class com.springboot2.learning.javabasic.java8.LambdaTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#29         // java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0:#34         // #0:add:()Lcom/springboot2/learning/javabasic/java8/Func;
   #3 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = InterfaceMethodref #37.#38        // com/springboot2/learning/javabasic/java8/Func.add:(II)I
   #5 = Methodref          #39.#40        // java/io/PrintStream.println:(I)V
   #6 = Class              #41            // com/springboot2/learning/javabasic/java8/LambdaTest
   #7 = Class              #42            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/springboot2/learning/javabasic/java8/LambdaTest;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               func
  #20 = Utf8               Lcom/springboot2/learning/javabasic/java8/Func;
  #21 = Utf8               MethodParameters
  #22 = Utf8               lambda$main$0
  #23 = Utf8               (II)I
  #24 = Utf8               x
  #25 = Utf8               I
  #26 = Utf8               y
  #27 = Utf8               SourceFile
  #28 = Utf8               LambdaTest.java
  #29 = NameAndType        #8:#9          // "<init>":()V
  #30 = Utf8               BootstrapMethods
  #31 = MethodHandle       #6:#43         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #32 = MethodType         #23            //  (II)I
  #33 = MethodHandle       #6:#44         // invokestatic com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
  #34 = NameAndType        #45:#46        // add:()Lcom/springboot2/learning/javabasic/java8/Func;
  #35 = Class              #47            // java/lang/System
  #36 = NameAndType        #48:#49        // out:Ljava/io/PrintStream;
  #37 = Class              #50            // com/springboot2/learning/javabasic/java8/Func
  #38 = NameAndType        #45:#23        // add:(II)I
  #39 = Class              #51            // java/io/PrintStream
  #40 = NameAndType        #52:#53        // println:(I)V
  #41 = Utf8               com/springboot2/learning/javabasic/java8/LambdaTest
  #42 = Utf8               java/lang/Object
  #43 = Methodref          #54.#55        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #44 = Methodref          #6.#56         // com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
  #45 = Utf8               add
  #46 = Utf8               ()Lcom/springboot2/learning/javabasic/java8/Func;
  #47 = Utf8               java/lang/System
  #48 = Utf8               out
  #49 = Utf8               Ljava/io/PrintStream;
  #50 = Utf8               com/springboot2/learning/javabasic/java8/Func
  #51 = Utf8               java/io/PrintStream
  #52 = Utf8               println
  #53 = Utf8               (I)V
  #54 = Class              #57            // java/lang/invoke/LambdaMetafactory
  #55 = NameAndType        #58:#62        // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #56 = NameAndType        #22:#23        // lambda$main$0:(II)I
  #57 = Utf8               java/lang/invoke/LambdaMetafactory
  #58 = Utf8               metafactory
  #59 = Class              #64            // java/lang/invoke/MethodHandles$Lookup
  #60 = Utf8               Lookup
  #61 = Utf8               InnerClasses
  #62 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #63 = Class              #65            // java/lang/invoke/MethodHandles
  #64 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #65 = Utf8               java/lang/invoke/MethodHandles
{
  public com.springboot2.learning.javabasic.java8.LambdaTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/springboot2/learning/javabasic/java8/LambdaTest;   public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:add:()Lcom/springboot2/learning/javabasic/java8/Func;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: iconst_1
        11: iconst_2
        12: invokeinterface #4,  3            // InterfaceMethod com/springboot2/learning/javabasic/java8/Func.add:(II)I
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 5: 0
        line 6: 6
        line 7: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  args   [Ljava/lang/String;
            6      15     1  func   Lcom/springboot2/learning/javabasic/java8/Func;
    MethodParameters:
      Name                           Flags
      args   private static int lambda$main$0(int, int);
    descriptor: (II)I
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0     x   I
            0       4     1     y   I
    MethodParameters:
      Name                           Flags
      x                              synthetic
      y                              synthetic
}
SourceFile: "LambdaTest.java"
InnerClasses:
     public static final #60= #59 of #63; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #32 (II)I
      #33 invokestatic com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I

反编译以后lambda表达式被编译成为一个lambda$main$0的函数,其实就是一段(x, y) -> x + y的方法,在看main方法主要分为以下8个步骤:

  1. 通过invokedynamic指令生成调用对象;
  2. 存入本地缓存;
  3. 加载java.lang.System.out静态方法;
  4. 将lambda表达式生成的对象加载入执行栈;
  5. 将int类型1加载入执行栈;
  6. 将int类型2加载入执行栈;
  7. 执行lambda表达式生成的对象的add方法;
  8. 输出执行结果;
    重点部分
    从mian方法中我们的重点就在于invokedynamic这个指令,重点要了解下是如何通过invokedynamic指令生成目标对象,invokedynamic指令通过找到BootstrapMethods中的方法,生成动态调用点,也是调用LambdaMetafactory的metafactory方法。
public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, 
                                             EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
}

通过源码可以看出,metafactory方法通过InnerClassLambdaMetafactory类生成对象,并提供后续调用,在InnerClassLambdaMetafactory源码中可以看到,有提供开关是否dump生成的class文件。



接下来我们通过设置启动参数-Djdk.internal.lambda.dumpProxyClasses查看中间对象,增加这个参数以后会生成LambdaTest$$Lambda$1类,

final class LambdaTest$$Lambda$1 implements Func {
    private LambdaTest$$Lambda$1() {
    }     @Hidden
    public int add(int var1, int var2) {
        return LambdaTest.lambda$main$0(var1, var2);
    }
}

我们再看下上面这个类反编译以后的情况

Classfile /Users/wangtongzhou/Documents/Java/learning/com/springboot2/learning/javabasic/java8/LambdaTest$$Lambda$1.class
  Last modified 2020-7-11; size 437 bytes
  MD5 checksum 729979930540708c60f4e71e63b69321
final class com.springboot2.learning.javabasic.java8.LambdaTest$$Lambda$1 implements com.springboot2.learning.javabasic.java8.Func
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
   #1 = Utf8               com/springboot2/learning/javabasic/java8/LambdaTest$$Lambda$1
   #2 = Class              #1             // com/springboot2/learning/javabasic/java8/LambdaTest$$Lambda$1
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               com/springboot2/learning/javabasic/java8/Func
   #6 = Class              #5             // com/springboot2/learning/javabasic/java8/Func
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               add
  #12 = Utf8               (II)I
  #13 = Utf8               Ljava/lang/invoke/LambdaForm$Hidden;
  #14 = Utf8               com/springboot2/learning/javabasic/java8/LambdaTest
  #15 = Class              #14            // com/springboot2/learning/javabasic/java8/LambdaTest
  #16 = Utf8               lambda$main$0
  #17 = NameAndType        #16:#12        // lambda$main$0:(II)I
  #18 = Methodref          #15.#17        // com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
  #19 = Utf8               Code
  #20 = Utf8               RuntimeVisibleAnnotations
{
  private com.springboot2.learning.javabasic.java8.LambdaTest$$Lambda$1();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return   public int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: invokestatic  #18                 // Method com/springboot2/learning/javabasic/java8/LambdaTest.lambda$main$0:(II)I
         5: ireturn
    RuntimeVisibleAnnotations:
      0: #13()
}

由此我们可以得出编译以后的代码为:

public class LambdaTest {
    public static void main(String[] args) {
       Func func= LambdaTest$$Lambda$1();
       System.out.println(func.add(1, 2));
    }
    private static int lambda$main$0(int x, int y) {
        return x + y;
    }     static final class LambdaTest$$Lambda$1 implements Func {
    private LambdaTest$$Lambda$1() {
    }     public int add(int x, inty) {
        return LambdaTest.lambda$main$0(x,y);
    }
  }
}

总结下,Lambda底层就是通过一个静态的内部类实现的;

结尾

欢迎大家点点关注,点点赞,感谢!

学习Java8系列-Lambda的更多相关文章

  1. Java8新特性系列-Lambda

    转载自:Java8新特性系列-Lambda – 微爱博客 Lambda Expressions in Java 8 Lambda 表达式是 Java 8 最流行的特性.它们将函数式编程概念引入 Jav ...

  2. Java8系列 (七) CompletableFuture异步编程

    概述 Java8之前用 Future 处理异步请求, 当你需要获取任务结果时, 通常的做法是调用  get(long timeout, TimeUnit unit) 此方法会阻塞当前的线程, 如果任务 ...

  3. Java8一Lambda与函数式接口

    关于Lambda表示在工作学习中会经常用到,但并没有全面的去了解.在这里做一个较为详细的记录供以后学习查阅.主要参考Java 8 Lambda 表达式 引言 Java8之前,我们在使用Runnale创 ...

  4. MongoDB学习笔记系列

    回到占占推荐博客索引 该来的总会来的,Ef,Redis,MVC甚至Sqlserver都有了自己的系列,MongoDB没有理由不去整理一下,这个系列都是平时在项目开发时总结出来的,希望可以为各位一些帮助 ...

  5. Nagios学习实践系列——基本安装篇

    开篇介绍 最近由于工作需要,学习研究了一下Nagios的安装.配置.使用,关于Nagios的介绍,可以参考我上篇随笔Nagios学习实践系列——产品介绍篇 实验环境 操作系统:Red Hat Ente ...

  6. Nagios学习实践系列——配置研究[监控当前服务器]

    其实上篇Nagios学习实践系列——基本安装篇只是安装了Nagios基本组件,虽然能够打开主页,但是如果不配置相关配置文件文件,那么左边菜单很多页面都打不开,相当于只是一个空壳子.接下来,我们来学习研 ...

  7. Dynamic CRM 2013学习笔记 系列汇总

    这里列出所有 Dynamic CRM 2013学习笔记 系列文章,方便大家查阅.有任何建议.意见.需要,欢迎大家提交评论一起讨论. 本文原文地址: Dynamic CRM 2013学习笔记 系列汇总 ...

  8. SQLServer学习笔记系列3

    一.写在前面的话 今天又是双休啦!生活依然再继续,当你停下来的时候,或许会突然显得不自在.有时候,看到一种东西,你会发现原来在这个社会上,优秀的人很多,默默 吃苦努力奋斗的人也多!星期五早上按时上班, ...

  9. SQLServer学习笔记系列2

    一.写在前面的话 继上一次SQLServer学习笔记系列1http://www.cnblogs.com/liupeng61624/p/4354983.html以后,继续学习Sqlserver,一步一步 ...

随机推荐

  1. 2019-02-05 Linux的一些常用命令学习2

    黑马程序员python课的笔记 ls -l 显示文件详细信息 ls -l -h 以k形式显示大小 ls -a 显示指定目录下的所有子目录和文件,包括隐藏文件 ls匹配符 *代表任意个数的字符 ?代表任 ...

  2. (五)使用logback进行日志记录

    原文:https://www.cnblogs.com/taiyonghai/p/9290641.html 引入jar包 此处如果是引用了spring boot则不需要再引一下的jar包了,spring ...

  3. macos的两个快捷键和一个小tip

    学校的linux协会介绍了一个免费的light轻量级加速器,昨天晚上十点左右的时候着手研究,发现其实就是一个代理服务器.在配置这个代理服务器的时候碰到了一些困难并最终都解决了.下面记录一下配置过程学到 ...

  4. VSCode + WSL 2 + Ruby环境搭建详解

    vscode配置ruby开发环境 vscode近年来发展迅速,几乎在3年之间就抢占了原来vim.sublime text的很多份额,犹记得在2015-2016年的时候,ruby推荐的开发环境基本上都是 ...

  5. Fabric网络组织与主节点选举

    一.Fabric网络组织 Fabric网络组织按如下结构组成:Fabric网络-->Channel通道-->组织(成员)-->节点.即整个网络由数个通道组成,每个通道都由多个组织构成 ...

  6. debug PostgreSQL 9.6.18 using Eclipse IDE on CentOS7

    目录 debug PostgreSQL 9.6.18 using Eclipse IDE on CentOS7 1.概览 2.建立用户 3.编译postgre 4.启动Eclipse 5.设置环境变量 ...

  7. 【服务器】CentOs7系统使用宝塔面板搭建网站,有FTP配置(保姆式教程)

    内容繁多,请耐心跟着流程走,在过程中遇到问题请在下面留言(我只是小白,请专业人士喷轻点). 这次用thinkphp5.1做演示,单纯的做演示,我打算下一篇文章用typecho(博客框架)演示. 前言 ...

  8. 【MonogDB帮助类】

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using MongoDB; ...

  9. 深入理解JVM(③)虚拟机的类加载器(双亲委派模型)

    前言 先解释一下什么是类加载器,通过一个类的全限定名来获取描述该类的二进制字节流,在虚拟机中实现这个动作的代码被称为"类加载器(Class Loader)". 类与类加载器 类加载 ...

  10. 10w行级别数据的Excel导入优化记录

    需求说明 项目中有一个 Excel 导入的需求:缴费记录导入 由实施 / 用户 将别的系统的数据填入我们系统中的 Excel 模板,应用将文件内容读取.校对.转换之后产生欠费数据.票据.票据详情并存储 ...