JDK 8新特性之Lambda表达式
创始人
2024-05-15 08:46:19
0

目录

一:使用匿名内部类存在的问题

Lambda表达式写法,代码如下:

二:Lambda的标准格式

三:Lambda的实现原理

四:Lambda省略格式

五:Lambda的前提条件

六:函数式接口

七:Lambda和匿名内部类对比


一:使用匿名内部类存在的问题

            当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。 传统写法,代码如下:
   new Thread(new Runnable() {@Overridepublic void run() {System.out.println("新线程执行代码啦");}}).start();

       由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。

    匿名内部类做了哪些事情:

         1.定义了一个没有名字的类
         2.这个类实现了Runnable接口
         3.创建了这个类的对象(new)

对于 Runnable 的匿名内部类用法,可以分析出几点内容:
  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类
  • 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在

           感觉代码很熟悉,但是我们最关注的是run方法和里面要执行的代码.,但是由于面向对象的特征,我们不得不弄一个类出来,去实现Runnable接口,重写run方法,new这个对象出来,可以体会到使用匿名内部类语法是很冗余的,因此,JDK 8根据这个问题提出一个新特性:Lambda表达式

              Lambda表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中(函数就是类中的方法),Lambda就是一个匿名函数,可以理解为一段可以传递的代码, 我们只需要将要执行的代码放到Lambda表达式中即可

Lambda表达式写法,代码如下:

           借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果
public class Demo01LambdaIntro {
public static void main(String[] args) {new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
}
}
             这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们 启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。 我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。

 Lambda表达式的好处: 可以简化匿名内部类,让代码更加精简   

 小结:              了解了匿名内部类语法冗余,体验了Lambda表达式的使用,发现Lmabda是简化匿名内部类的简写

二:Lambda的标准格式

      Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:
(参数类型 参数名称) -> {代码体;
}
  格式说明:
  • (参数类型 参数名称):参数列表
  • {代码体;}:方法体
  • -> :箭头,分隔参数列表和方法体,起到连接作用
   Lambda与方法的对比             匿名内部类
public void run() {System.out.println("aa");
}
            Lambda
() -> System.out.println("bb!")

  • 练习无参数无返回值的Lambda
掌握了Lambda的语法,我们来通过一个案例熟悉Lambda的使用。   定义一个接口,并定义一个抽象方法
interface Swimmable {public abstract void swimming();
}
public class Demo02LambdaUse {public static void main(String[] args) {//传统方式goSwimming(new Swimmable() {@Overridepublic void swimming() {System.out.println("凤姐 自由泳.");}});//Lambda表达式goSwimming(() -> {System.out.println("如花 蛙泳");});}// 小结:以后我们看到方法的参数是接口就可以考虑使用Lambda表达式//  Lambda表达式相当于是对接口抽象方法的重写// 练习无参数无返回值的Lambdapublic static void goSwimming(Swimmable s) {s.swimming();}}

 

  • 练习有参数有返回值的Lambda

定义一个接口,并定义一个抽象方法,带有参数

interface Smokeable {public abstract int smoking(String name);
}
 goSmoking(new Smokeable() {@Overridepublic void smoking() {System.out.println("有一根"+name+"的烟");return 5;}});goSmoking((String name) ->{System.out.println("Lambda 有"+name+"的烟");return 6;});// 练习有参数有返回值的Lambdapublic static void goSmoking(Smokeable s) {int i= s.smoking("China made");System.out.println(i);}
下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
  • public abstract int compare(T o1, T o2);
            当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。 传统写法       如果使用传统的代码对 ArrayList 集合进行排序,写法如下:
public class Person {
private String name;
private int age;
private int height;
// 省略其他
}
// 练习有参数有返回值的Lambda
public class Demo03LambdaParam {public static void main(String[] args) {ArrayList persons = new ArrayList<>();persons.add(new Person("刘德华", 58, 174));persons.add(new Person("张学友", 58, 176));persons.add(new Person("刘德华", 54, 171));persons.add(new Person("黎明", 53, 178));// 对集合中的数据进行排序/*Collections.sort(persons, new Comparator() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge(); // 升序排序}});*/Collections.sort(persons, (Person o1, Person o2) -> {return o2.getAge() - o1.getAge(); // 降序});for (Person person : persons) {System.out.println(person);}System.out.println("-----------");persons.forEach((t) -> {System.out.println(t);});}
}
小结:          首先学习了Lambda表达式的标准格式
(参数列表) -> {方法体;
}
         以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重写

三:Lambda的实现原理

         我们现在已经会使用Lambda表达式了。现在同学们肯定很好奇Lambda是如何实现的,现在我们就来探究Lambda表达式的底层实现原理。
@FunctionalInterface
interface Swimmable {
public abstract void swimming();
}
public class Demo04LambdaImpl {
public static void main(String[] args) {goSwimming(new Swimmable() {@Overridepublic void swimming() {System.out.println("使用匿名内部类实现游泳");
}
});
}public static void goSwimming(Swimmable swimmable) {swimmable.swimming();
}
}
我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class

 使用XJad反编译这个类,得到如下代码:

package com.tangyuan.demo01lambda;
import java.io.PrintStream;
// Referenced classes of package com.itheima.demo01lambda:
// Swimmable, Demo04LambdaImpl
static class Demo04LambdaImpl$1 implements Swimmable {public void swimming()
{System.out.println("使用匿名内部类实现游泳");
}Demo04LambdaImpl$1() {
}
}
我们再来看看Lambda的效果,修改代码如下:
public class Demo04LambdaImpl {
public static void main(String[] args) {goSwimming(() -> {System.out.println("Lambda游泳");});
}
public static void goSwimming(Swimmable swimmable) {swimmable.swimming();
}
}
     运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一 个新的类。使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用 JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。 在DOS命令行输入:
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员
反汇编后效果如下:
C:\Users\>javap -c -p Demo04LambdaImpl.class
Compiled from "Demo04LambdaImpl.java"
public class com.itheima.demo01lambda.Demo04LambdaImpl {
public com.tangyuan.demo01lambda.Demo04LambdaImpl();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:
()Lcom/tangyuan/demo01lambda/Swimmable;
5: invokestatic #3 // Method goSwimming:
(Lcom/tangyuan/demo01lambda/Swimmable;)V
8: return
public static void goSwimming(com.itheima.demo01lambda.Swimmable);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethod
com/tangyuan/demo01lambda/Swimmable.swimming:()V
6: return
private static void lambda$main$0();
Code:
0: getstatic #5 // Field
java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Lambda游泳
5: invokevirtual #7 // Method java/io/PrintStream.println:
(Ljava/lang/String;)V
8: return
}
       可以看到在类中多出了一个私有的静态方法 lambda$main$0 。这个方法里面放的是什么内容呢?我们通过断点调试来看看:

       可以确认 lambda$main$0 里面放的就是Lambda中的内容,我们可以这么理解 lambda$main$0 方法:

public class Demo04LambdaImpl {
public static void main(String[] args) {
...
}
private static void lambda$main$0() {
System.out.println("Lambda游泳");
}
}
             关于这个方法 lambda$main$0 的命名:以lambda开头,因为是在main()函数里使用了lambda表达式,所以带有 $main表示,因为是第一个,所以$0。           如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加 上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文 件中。使用java命令如下:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
根据上面的格式,在命令行输入以下命令:
C:\Users\>java -Djdk.internal.lambda.dumpProxyClasses
com.tangyuan.demo01lambda.Demo04LambdaImpl
Lambda游泳

 执行完毕,可以看到生成一个新的类,效果如下:

 反编译 Demo04LambdaImpl$$Lambda$1.class 这个字节码文件,内容如下:

// Referenced classes of package com.tnagyuan.demo01lambda:
// Swimmable, Demo04LambdaImpl
final class Demo04LambdaImpl$$Lambda$1 implements Swimmable {
public void swimming()
{
Demo04LambdaImpl.lambda$main$0();
}
private Demo04LambdaImpl$$Lambda$1()
{
}
}
        可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, swimming 方法调用 Demo04LambdaImpl.lambda$main$0() ,也就是调用Lambda中的内容。最后可以将Lambda理解为:
public class Demo04LambdaImpl {
public static void main(String[] args) {goSwimming(new Swimmable() {public void swimming() {Demo04LambdaImpl.lambda$main$0();
}
});
}
private static void lambda$main$0() {System.out.println("Lambda表达式游泳");}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
小结:         匿名内部类在编译的时候会一个class文件         Lambda在程序运行的时候形成一个类 1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码 2. 还会形成一个匿名内部类,实现接口,重写抽象方法 3. 在接口的重写方法中会调用新生成的方法

四:Lambda省略格式

在Lambda标准格式的基础上,使用省略写法的规则为: 1. 小括号内参数的类型可以省略 2. 如果小括号内有且仅有一个参数,则小括号可以省略 3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> {
return new Person();
}
省略后
a -> new Person()

案例如下:

   public static void main(String[] args) {ArrayList persons = new ArrayList<>();persons.add(new Person("刘德华", 58, 174));persons.add(new Person("张学友", 58, 176));persons.add(new Person("刘德华", 54, 171));persons.add(new Person("黎明", 53, 178));----------------------------省略前--------------Collections.sort(persons, (o1, o2) -> {return o1.getAge() - o2.getAge();});for (Person person : persons) {System.out.println(person);}---------------------------------省略后---------------------------Collections.sort(persons, (o1, o2) -> o2.getAge() - o1.getAge());persons.forEach(t -> System.out.println(t));}

五:Lambda的前提条件

Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意: 1. 方法的参数或局部变量类型必须为接口才能使用Lambda 2. 接口中有且仅有一个抽象方法
public interface Flyable {public abstract void flying();
}
public class Demo06LambdaCondition {public static void main(String[] args) {// 方法的参数或局部变量类型必须为接口才能使用Lambdatest(() -> {});Runnable r = new Runnable() {@Overridepublic void run() {System.out.println("aa");}};Flyable f = () -> {System.out.println("我会飞啦");};}public static void test(Flyable a) {new Person() {};}}// 只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda
@FunctionalInterface // 检测这个接口是不是只有一个抽象方法
interface Flyable {// 接口中有且仅有一个抽象方法public abstract void eat();// public abstract void eat2();
}
小结: Lambda表达式的前提条件: 1. 方法的参数或变量的类型是接口 2. 这个接口中只能有一个抽象方法

六:函数式接口

              函数式接口在Java中是指:有且仅有一个抽象方法的接口。               函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以 适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。               FunctionalInterface注解 与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于一个接口的定义上:
@FunctionalInterface
public interface Operator {void myMethod();
}
               一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样

七:Lambda和匿名内部类对比

       Lambda和匿名内部类在使用上的区别 1. 所需的类型不一样 匿名内部类,需要的类型可以是类,抽象类,接口 Lambda表达式,需要的类型必须是接口 2. 抽象方法的数量不一样 匿名内部类所需的接口中抽象方法的数量随意 Lambda表达式所需的接口只能有一个抽象方法 3. 实现原理不同 匿名内部类是在编译后会形成class Lambda表达式是在程序运行的时候动态生成class 小结    当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...