JDK8新特性-lambda表达式

初体验

什么是lambda表达式呢?不墨迹,直接上最经典的线程来进行初体验

没使用lambda表达式时,我们创建线程的写法

package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
    public static void main(String[] args) {
        // 普通写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程被创建:"+Thread.currentThread().getName());
            }
        }).start();
    }
}
复制代码

代码分析,发现问题

  1. Thread类需要一个Runnable接口做为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
  2. 为了指定run方法体,不得不需要Runnable的实现类
  3. 为了省去定义一个Runnable的实现类,不得不使用匿名内部类
  4. 必须覆盖重写抽象的run方法所有的方法名称,方法参数,方法返回值不得不重写一遍,而且不能出错,但是,我们只关心run方法体内的代码
  • 因此,lambda应这个问题而产生

lambda表达式初体验–创建新线程

package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
    public static void main(String[] args) {
        // lambda写法
        new Thread(()->{
            System.out.println("新线程被创建:"+Thread.currentThread().getName());
        }).start();
    }
}

复制代码
  • 可以发现
  1. lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
  2. lambda表达式简化了匿名内部类的使用,语法更加简单。
  3. lambda表达式使得代码更加的简洁

lambda表达式语法

lambda表达式组成

lambda表达式由三部分组成: () -> {}

(参数列表) -> {方法体代码}
复制代码
  • ()中填参数
  • -> 固定写法,分割参数列表和方法体代码
  • {}中编写我们方法体的代码

lambda表达式的使用条件

  1. 方法的参数或者局部变量类型必须为接口才能使用
  2. 接口中有且仅有一个抽象方法@FunctionalInterface

image.png

@FunctionalInterface注解

  1. 一种信息性注释类型,用于指示接口类型声明是Java语言规范所定义的功能接口。从概念上讲,一个功能性接口只有一个抽象方法因为默认方法有一个实现,所以它们不是抽象的。如果接口声明了一个抽象方法,则该方法将覆盖java.lang的一个公共方法。对象,这也不计入接口的抽象方法计数,因为接口的任何实现都将有来自java.lang.Object或其他地方的实现。

  2. 注意,函数接口的实例可以用lambda表达式、方法引用或构造函数引用来创建

  3. 如果一个类型是用这个注释类型注释的,编译器需要生成一个错误消息,除非:

    • 该类型是接口类型,而不是注释类型、枚举或类。
    • 带注释的类型满足功能性接口的需求。
  4. 但是,编译器将把任何满足函数接口定义的接口视为函数接口,而不管接口声明中是否存在FunctionalInterface注释。

image.png

总而言之,@FunctionalInterface注解就是用来保证该接口只允许定义一个抽象方法

image.png

lambda表达式缩写

在lambda表达式的标准写法基础上,可以使用省略写法的规则为:

  1. 小括号内的参数类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号

就好比如在创建线程的时候

new Thread(
    () -> System.out.println("新线程被创建:"+Thread.currentThread().getName())
).start();
复制代码

lambda vs 匿名内部类

  1. 所需类型不一样
  • 匿名内部类的类型可以是类,抽象类,接口
  • Lambda表达式需要的类型必须是接口
  1. 抽象方法的数量不—样
  • 匿名内部类所需的接口中的抽象方法的数量是随意的
  • Lambda表达式所需的接口中只能有一个抽象方法
  1. 实现原理不—样
  • 匿名内部类是在编译后形成一个class
  • Lambda表达式是在程序运行的时候动态生成class

lambda原理分析

我们以创建线程为例进行分析

方式一

package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
    public static void main(String[] args) {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("新线程被创建:"+Thread.currentThread().getName());
             }
         }).start();
    }
}

复制代码

方式二

package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("新线程被创建:"+Thread.currentThread().getName());
        }).start();
    }
}

复制代码

运行,通过方式一会生成demo01$1.classdemo01.class两个文件,其中,demo01$1.class文件是由匿名内部类产生的,demo01$1.class文件是匿名内部类的本质实现;而通过方式二运行,则只会生成demo01.class,这就说明了lambda表达式并未生成匿名内部类class文件,也就是说java8并不是靠编译器将lambda转换为匿名内部类

image.png

这时候,我们通过JDK自带的工具javap对demo01.class进行反编译(注意,要找到demo01.class的文件位置,在那里执行命令)

image.png

image.png

输入命令进行反编译

javap -c -p 文件名.class
-c 对代码进行反编译 (-c也可以换成-v,-v的信息更加详细)
-p 显示所有的类和成员
复制代码

image.png

反编译的结果:

D:\Users\ASUS\JDK8-new-features\target\classes\com\features\lambda\demo01>javap -c -p demo01.class
Compiled from "demo01.java"
public class com.features.lambda.demo01.demo01 {
  public com.features.lambda.demo01.demo01();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/Thread
       3: dup
       4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      12: invokevirtual #5                  // Method java/lang/Thread.start:()V
      15: return

  private static void lambda$main$0();
    Code:
       0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #7                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #9                  // String 新线程被创建:
      12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: invokestatic  #11                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      18: invokevirtual #12                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      21: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      30: return
}
复制代码

在反编译的结果中,我们看到了它创建了一个private static void lambda$main$0();方法,其实,这应该就是lambda表达式所对应的方法,我们可以通过debug的方式进行测试

image.png

也就是相当于lambda$main$0()静态方法如下

 private static void lambda$main$0(){
     System.out.println("新线程被创建:"+Thread.currentThread().getName());
 }
 
复制代码

为了更加直观的理解这个它,我们可以在运行的时候添加Java -Djdk.internallambda.dumpProxyClasses 要运行的包名.类名命令,加上这个参数会将内部class码输出到一个文件中(注意,该命令运行的位置是在classes的位置)

D:\Users\ASUS\JDK8-new-features\target\classes>java -Djdk.internallambda.dumpProxyClasses com.features.lambda.demo01.demo01
复制代码

结果:

image.png

小结:

匿名内部类在编译的时候会产生—个class文件。

Lambda表达式在程序运行的时候会形成一个类。

  1. 在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码,即private static void lambda$main$0()方法
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法
  3. 在接口中重写方法会调用新生成的方法
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享