JDK 8新特性之Lambda表达式

news2024/9/27 21:28:53

目录

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

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

二:Lambda的标准格式

三:Lambda的实现原理

四:Lambda省略格式

五:Lambda的前提条件

六:函数式接口

七:Lambda和匿名内部类对比


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

            当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法 , 代码如下:
   new Thread(new Runnable() {
            @Override
            public 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() {
            @Override
            public void swimming() {
                System.out.println("凤姐 自由泳.");
            }
        });

         
       //Lambda表达式
        goSwimming(() -> {
            System.out.println("如花 蛙泳");
        });
    }


      // 小结:以后我们看到方法的参数是接口就可以考虑使用Lambda表达式
     //  Lambda表达式相当于是对接口抽象方法的重写

    // 练习无参数无返回值的Lambda
    public static void goSwimming(Swimmable s) {
        s.swimming();
    }

   
}

 

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

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

interface Smokeable {
     public abstract int smoking(String name);
}
 
 goSmoking(new Smokeable() {
            @Override
            public void smoking() {
                System.out.println("有一根"+name+"的烟");
                 return 5;
            }
        });


 goSmoking((String name) ->{
       System.out.println("Lambda 有"+name+"的烟");
         return 6;
   });


 // 练习有参数有返回值的Lambda
    public static void goSmoking(Smokeable s) {
      int i= s.smoking("China made");
     System.out.println(i);
    }
下面举例演示 java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:
  • 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<Person> 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<Person>() {
            @Override
            public 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() {
      @Override
    public 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."<init>":()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<Person> 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) {
        // 方法的参数或局部变量类型必须为接口才能使用Lambda
        test(() -> {
        });

        Runnable r = new Runnable() {
            @Override
            public 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 表达式 , 其他其他情况还是需要使用匿名内部类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

05回溯法

文章目录装载问题回溯算法优化算法构造最优解0-1背包问题批处理作业调度问题图的M着色问题N皇后问题最大团问题回溯算法实际上一个类似枚举的搜索尝试过程&#xff0c;主要是在搜索尝试过程中寻找问题的解&#xff0c;当发现已不满足求解条件时&#xff0c;就“回溯”返回&…

12. 字典dict类型详解

1. 基础知识 (1) 字典(dictionary)是Python中另一个非常有用的内置数据类型。 (2) 列表是有序的对象集合&#xff0c;字典是无序的对象集合。两者之间的区别在于&#xff1a;字典当中的元素是通过键来存取的&#xff0c;而不是通过偏移存取。 (3) 字典是一种映射类型&#xff…

Flowable进阶学习(三)流程、流程实例挂起与激活;启动、处理、结束流程的原理以及相关表结构与变动

文章目录流程挂起与激活流程实例挂起与激活启动、处理、结束流程的原理一、启动流程的原理启动一个流程实例时涉及到的表及表结构:ACT_RU_EXECUTION 运行时流程执行实例ACT_RU_IDENTITYLINK 运行时用户关系信息ACT_RU_TASK 运行时任务表ACT_RU_VARIABLE 运行时变量表二、处理流…

过滤器Filter总结

过滤器Filter1. 简介2. 快速入门3. 执行流程4. 使用细节4.1 拦截路径4.2 过滤器链5. 案例5.1 需求5.2 LoginFilter1. 简介 过滤器是JavaWeb三大组件之一&#xff08;Servlet、Filter&#xff0c;Listner&#xff09;&#xff1b; 作用&#xff1a; 把对资源&#xff08;servl…

Ubuntu22.04 安装 ssh

文章目录Ubuntu22.04 安装 ssh一、 环境配置二、 启动远程连接三、 开放端口四、 远程连接Ubuntu22.04 安装 ssh 一、 环境配置 安装 Ubuntu 系统后&#xff0c;我们首先需要配置管理员 root 用户&#xff1a; sudo passwd root然后&#xff0c;进行软件源的更换&#xff1a…

14 Java集合(Map集合+HashMap+泛型使用+集合面试题)

集合14.11 Map集合14.11.1 Map集合特点14.11.2 Map集合体系结构14.12 HashMap14.12.1 HashMap基本使用14.12.2 HashMap实际应用14.12.3 HashMap练习14.12.4 HashMap底层实现原理14.12.5 put的过程原码14.12.6 resize过程原码14.12.7 get的过程原码14.13 HashTable14.14 泛型高级…

5-1中央处理器-CPU的功能和基本结构

文章目录一.CPU的功能二.CPU的基本结构&#xff08;一&#xff09;运算器1.运算器的基本组成2.专用数据通路方式3.CPU内部单总线方式&#xff08;二&#xff09;控制器1.基本组成2.实现过程&#xff08;三&#xff09;寄存器一.CPU的功能 中央处理器&#xff08;CPU&#xff0…

并查集的入门与应用

目录 一、前言 二、并查集概念 1、并查集的初始化 2、并查集的合并 3、并查集的查找 4、初始化、查找、合并代码 5、复杂度 二、路径压缩 三、例题 1、蓝桥幼儿园&#xff08;lanqiaoOJ题号1135&#xff09; 2、合根植物&#xff08;2017年决赛&#xff0c;lanqiaoO…

SQL注入篇 - 布尔盲注及延时注入

数据来源 盲注 什么是盲注&#xff1a; 布尔盲注原理 布尔盲注流程 手工盲注思路&#xff08;以下的文章参考&#xff1a;DVWA-sql注入&#xff08;盲注&#xff09; - N0r4h - 博客园&#xff09; 手工盲注的过程&#xff0c;就像你与一个机器人聊天&#xff0c;这个机器人知…

DGSEA | GSEA做完了不要停,再继续比较一下有意义的通路吧!~

1写在前面 GSEA大家都会用了&#xff0c;但GSEA也有它自己的缺点&#xff0c;就是不能比较两个基因集或通路的富集情况。&#x1f912; 今天介绍一个Differential Gene Set Enrichment Analysis (DGSEA)&#xff0c;可以量化两个基因集的相对富集程度。&#x1f609; 2用到的包…

Java中的位运算及其常见的应用

文章目录1、位运算1.1 原码、反码、补码1.2 位运算符2、位运算的应用2.1 取模运算2.2 奇偶性判断2.3 交换变量的值2.4 加法运算1、位运算 1.1 原码、反码、补码 计算机中所有数据的存储和运算都是以二进制补码的形式进行的。a —> 97&#xff0c;A —> 65&#xff0c;‘…

深入学习Vue.js(十二)编译器

模板DSL的编译器 1.编译器概述 编译器实际上是一段程序&#xff0c;他用来将一种语言A翻译为另一种语言B。其中&#xff0c;A被称为源代码&#xff0c;B被称为目标代码。编译器将源代码翻译为目标代码的过程被称为编译。完整的编译过程通常包含词法分析、语法分析、语义分析、…

软件测试——测试用例

作者&#xff1a;~小明学编程 文章专栏&#xff1a;测试开发 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 测试用例的设计方法 等价类 边界值 错误猜测法 判定表法&#xff08;使用于关系组合&#xff09; 设计步骤 具体例子 正交法 场景设计法…

Redis相关简介

1. Redis 简介 在这个部分&#xff0c;我们将学习以下3个部分的内容&#xff0c;分别是&#xff1a; ◆ Redis 简介&#xff08;NoSQL概念、Redis概念&#xff09; ◆ Redis 的下载与安装 ◆ Redis 的基本操作 1.1 NoSQL概念 1.1.1 问题现象 在讲解NoSQL的概念之前呢&am…

8. R语言画:散点图、直方图、条形图、箱线图、小提琴图、韦恩图

b站课程视频链接&#xff1a; https://www.bilibili.com/video/BV19x411X7C6?p1 腾讯课堂(最新&#xff0c;但是要花钱&#xff0c;我花99&#x1f622;&#x1f622;元买了&#xff0c;感觉讲的没问题&#xff0c;就是知识点结构有点乱&#xff0c;有点废话&#xff09;&…

九大数据分析方法-综合型分析方法以及如何使用这九大分析方法

文章目录3 综合型分析方法3.1 相关性分析法3.1.1 直接相关3.1.2 间接相关3.2标签分析法3.3 MECE法4 如何使用九大方法本文来源&#xff0c;为接地气的陈老师的知识星球&#xff0c;以及付同学的观看笔记。3 综合型分析方法 3.1 相关性分析法 相关性分析法&#xff1a;寻找指标…

ROS2机器人编程简述humble-第二章-Executors .3.5

ROS2机器人编程简述humble-第二章-Parameters .3.4由于ROS2中的节点是C对象&#xff0c;因此一个进程可以有多个节点。事实上&#xff0c;在许多情况下&#xff0c;这样做是非常有益的&#xff0c;因为当通信处于同一进程中时&#xff0c;可以通过使用共享内存策略来加速通信。…

freeglut 在mfc 下的编译

freeglut 是OpenGL Utility Toolkit (GLUT) library 的替代版本&#xff0c;glut 应用广阔&#xff0c;但比较陈旧&#xff0c;很久没有更新。 我原来的opengl 用的是glut&#xff0c; 想更新到64位版本&#xff0c;怎么也找不到合适的下载。最后找到完全替代版本freeglut。fre…

【Linux】线程概念 | 互斥

千呼万唤始出来&#xff0c;终于到多线程方面的学习了&#xff01; 所用系统Centos7.6 本文的源码&#x1f449;【传送门】 最近主要是在我的hexo个人博客上更新&#xff0c;csdn的更新会滞后 文章目录1.线程的概念1.1 执行流1.2 线程创建时做了什么&#xff1f;1.3 内核源码中…

每刻和金蝶云星空接口打通对接实战

接通系统&#xff1a;每刻3000中大型企业在用&#xff0c;新一代业财税一体化解决方案提供商。旗下拥有每刻报销、每刻档案、每刻云票、每刻财务共享云平台等&#xff0c;助力企业实现财务数字化转型。对接系统&#xff1a;金蝶云星空金蝶K/3Cloud结合当今先进管理理论和数十万…