Lambda表达式超详细总结

article/2025/10/4 2:06:38

文章目录

    • 1. 什么是Lambda表达式
    • 2. 为什么使用Lambda表达式
    • 3. Lambda表达式语法
    • 4. 函数式接口
      • 4.1 什么是函数式接口
      • 4.2 自定义函数式接口
      • 4.3 Java内置函数式接口
    • 5. 方法引用
    • 6. 构造器引用
    • 7. 数组引用
    • 8. Lambda表达式的作用域
      • 8.1 访问局部变量
      • 8.2 访问局部引用,静态变量,实例变量
      • 8.3 Lambda表达式访问局部变量作限制的原因
    • 9. Lambda表达式的优缺点

1. 什么是Lambda表达式

Lambda表达式,也可称为闭包。类似于JavaScript中的闭包,它是推动Java8发布的最重要的新特性。

2. 为什么使用Lambda表达式

我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。Lambda允许把函数作为一个方法的参数,使用Lambda表达式可以写出更简洁、更灵活的代码,而其作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

一个简单示例:

分别使用成员内部类、局部内部类、静态内部类、匿名内部类方式实现Runnable的run()方法并创建和启动线程,如下所示:

public class LambdaDemo {/*** 成员内部类*/class MyThread01 implements Runnable{@Overridepublic void run() {System.out.println("成员内部类:用Lambda语法创建线程吧!");}}/*** 静态内部类*/static class MyThread02 implements Runnable{@Overridepublic void run() {System.out.println("静态内部类:对啊,用Lambda语法创建线程吧!");}}public static void main(String[] args) {/*** 局部内部类*/class MyThread03 implements Runnable{@Overridepublic void run() {System.out.println("局部内部类:用Lambda语法创建线程吧!");}}/*** 匿名内部类*/Runnable runnable = new Runnable(){@Overridepublic void run() {System.out.println("匿名内部类:求求你,用Lambda语法创建线程吧!");}};//成员内部类方式LambdaDemo lambdaDemo = new LambdaDemo();MyThread01 myThread01 =lambdaDemo.new MyThread01();new Thread(myThread01).start();//静态内部类方式MyThread02 myThread02 = new MyThread02();new Thread(myThread02).start();//局部内部类MyThread03 myThread03 = new MyThread03();new Thread(myThread03).start();//匿名内部类的方式new Thread(runnable).start();}
}

执行结果:
在这里插入图片描述

可以看到上面创建方式,代码量都不少,使用Lambda表达式实现,如下所示:

 //Lambda方式
new Thread(() -> System.out.println("使用Lambda就对了")).start();

可以看到代码明显简洁了许多。
在这里插入图片描述

3. Lambda表达式语法

Lambda表达式在Java语言中引入了一个操作符**“->”**,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:

  • 左侧:指定了Lambda表达式需要的所有参数
  • 右侧:制定了Lambda体,即Lambda表达式要执行的功能。

像这样:

(parameters) -> expression
或
(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但无参数或多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

下面对每个语法格式的特征进行举例说明:

(1)语法格式一:无参,无返回值,Lambda体只需一条语句。如下:

  @Testpublic void test01(){Runnable runnable=()-> System.out.println("Runnable 运行");runnable.run();//结果:Runnable 运行}

(2)语法格式二:Lambda需要一个参数,无返回值。如下:

  @Testpublic void test02(){Consumer<String> consumer=(x)-> System.out.println(x);consumer.accept("Hello Consumer");//结果:Hello Consumer}

(3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:

  public void test02(){Consumer<String> consumer=x-> System.out.println(x);consumer.accept("Hello Consumer");//结果:Hello Consumer}

(4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。

  @Testpublic void test04(){Comparator<Integer> com=(x, y)->{System.out.println("函数式接口");return Integer.compare(x,y);};System.out.println(com.compare(2,4));//结果:-1}

(5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写

  @Testpublic void test05(){Comparator<Integer> com=(x, y)-> Integer.compare(x,y);System.out.println(com.compare(4,2));//结果:1}

(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断”

  @Testpublic void test06(){Comparator<Integer> com=(Integer x, Integer y)-> Integer.compare(x,y);System.out.println(com.compare(4,2));//结果:1}

类型推断:在执行javac编译程序时,JVM根据程序的上下文推断出了参数的类型。Lambda表达式依赖于上下文环境。

语法背诵口诀:左右遇一括号省,左侧推断类型省,能省则省。

4. 函数式接口

4.1 什么是函数式接口

==只包含一个抽象方法的接口,就称为函数式接口。==我们可以通过Lambda表达式来创建该接口的实现对象。
我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

4.2 自定义函数式接口

按照函数式接口的定义,自定义一个函数式接口,如下:

@FunctionalInterface
public interface MyFuncInterf<T> {public T getValue(String origin);
}

定义一个方法将函数式接口作为方法参数。

  public String toLowerString(MyFuncInterf<String> mf,String origin){return mf.getValue(origin);}

将Lambda表达式实现的接口作为参数传递。

  public void test07(){String value=toLowerString((str)->{return str.toLowerCase();},"ABC");System.out.println(value);//结果ABC}

4.3 Java内置函数式接口

四大核心函数式接口的介绍,如图所示:
在这里插入图片描述
使用示例:
1.Consumer:消费型接口 void accept(T t)

  public void makeMoney(Integer money, Consumer<Integer> consumer){consumer.accept(money);}@Testpublic void test01(){makeMoney(100,t-> System.out.println("今天赚了"+t));//结果:今天赚了100}

2.Supplier:供给型接口 T get()

  /*** 产生指定的整数集合放到集合中* Iterable接口的forEach方法的定义:方法中使用到了Consumer消费型接口,*     default void forEach(Consumer<? super T> action) {*         Objects.requireNonNull(action);*         for (T t : this) {*             action.accept(t);*         }*     }*/@Testpublic void test02(){List list = addNumInList(10, () -> (int) (Math.random() * 100));list.forEach(t-> System.out.println(t));}public List addNumInList(int size, Supplier<Integer> supplier){List<Integer> list=new ArrayList();for (int i = 0; i < size; i++) {list.add(supplier.get());}return list;}

3.Function<T,R>:函数型接口 R apply(T t)

 /*** * 使用函数式接口处理字符串。*/public String handleStr(String s,Function<String,String> f){return f.apply(s);}@Testpublic void test03(){System.out.println(handleStr("abc",(String s)->s.toUpperCase()));}//结果:ABC

4.Predicate:断言型接口 boolean test(T t)

  /***  自定义条件过滤字符串集合*/@Testpublic void test04(){List<String> strings = Arrays.asList("啊啊啊", "2333", "666", "?????????");List<String> stringList = filterStr(strings, (s) -> s.length() > 3);for (String s : stringList) {System.out.println(s);}}public List<String> filterStr(List<String> list, Predicate<String> predicate){ArrayList result = new ArrayList();for (int i = 0; i < list.size(); i++) {if (predicate.test(list.get(i))){result.add(list.get(i));}}return result;}

其他接口的定义,如图所示:
在这里插入图片描述

5. 方法引用

当要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表一致,方法的返回值也必须一致,即方法的签名一致)。方法引用可以理解为方法引用是Lambda表达式的另外一种表现形式。
方法引用的语法:使用操作符“::”将对象或类和方法名分隔开。
方法引用的使用情况共分为以下三种:

  • 对象::实例方法名
  • 类::静态方法名
  • 类::实例方法名

使用示例:
1.对象::实例方法名

  /***PrintStream中的println方法定义 *     public void println(String x) {*         synchronized (this) {*             print(x);*             newLine();*         }*     }*///对象::实例方法名@Testpublic void test1(){PrintStream out = System.out;Consumer<String> consumer=out::println;consumer.accept("hello");}
  1. 类::静态方法名
    /*** Integer类中的静态方法compare的定义:*     public static int compare(int x, int y) {*         return (x < y) ? -1 : ((x == y) ? 0 : 1);*     }*/@Testpublic void test2(){Comparator<Integer> comparable=(x,y)->Integer.compare(x,y);//使用方法引用实现相同效果Comparator<Integer> integerComparable=Integer::compare;System.out.println(integerComparable.compare(4,2));//结果:1System.out.println(comparable.compare(4,2));//结果:1}

3.类::实例方法名

  @Testpublic void test3(){BiPredicate<String,String> bp=(x,y)->x.equals(y);//使用方法引用实现相同效果BiPredicate<String,String> bp2=String::equals;System.out.println(bp.test("1","2"));//结果:falseSystem.out.println(bp.test("1","2"));//结果:false}

6. 构造器引用

格式:类名::new
与函数式接口相结合,自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法。需要注意构造器参数列表要与接口中抽象方法的参数列表一致。使用示例:
创建一个实体类Employee:

public class Employee {private Integer id;private String name;private Integer age;@Overridepublic String toString() {return "Employee{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}public Employee(){}public Employee(Integer id) {this.id = id;}public Employee(Integer id, Integer age) {this.id = id;this.age = age;}public Employee(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}
}

使用构造器引用与函数式接口相结合

  @Testpublic void test01(){//引用无参构造器Supplier<Employee> supplier=Employee::new;System.out.println(supplier.get());//引用有参构造器Function<Integer,Employee> function=Employee::new;System.out.println(function.apply(21));BiFunction<Integer,Integer,Employee> biFunction=Employee::new;System.out.println(biFunction.apply(8,24));}输出结果:Employee{id=null, name='null', age=null}Employee{id=21, name='null', age=null}Employee{id=8, name='null', age=24}

7. 数组引用

数组引用的格式:type[]:new
使用示例:

  @Testpublic void test02(){Function<Integer,String[]> function=String[]::new;String[] apply = function.apply(10);System.out.println(apply.length);//结果:10}

8. Lambda表达式的作用域

Lambda表达式可以看作是匿名内部类实例化的对象,Lambda表达式对变量的访问限制和匿名内部类一样,因此Lambda表达式可以访问局部变量、局部引用,静态变量,实例变量。

8.1 访问局部变量

在Lambda表达式中规定只能引用标记了final的外层局部变量。我们不能在lambda 内部修改定义在域外的局部变量,否则会编译错误。

public class TestFinalVariable {interface VarTestInterface{Integer change(String str);}public static void main(String[] args) {//局部变量不使用final修饰Integer tempInt = 1;VarTestInterface var = (str -> Integer.valueOf(str+tempInt));//再次修改,不符合隐式final定义tempInt =2;Integer str =var.change("111") ;System.out.println(str);}
}

上面代码会出现编译错误,出现如下提示:
在这里插入图片描述

特殊情况下,局部变量也可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

例如上面的代码确保Lambda表达式后局部变量后面不做修改,就可以成功啦!

public class TestFinalVariable {interface VarTestInterface{Integer change(String str);}public static void main(String[] args) {//局部变量不使用final修饰Integer tempInt = 1;VarTestInterface var = (str -> Integer.valueOf(str+tempInt));Integer str =var.change("111") ;System.out.println(str);}
}

8.2 访问局部引用,静态变量,实例变量

Lambda表达式不限制访问局部引用变量,静态变量,实例变量。代码测试都可正常执行,代码:

public class LambdaScopeTest {/*** 静态变量*/private static String staticVar;/*** 实例变量*/private String instanceVar;@FunctionalInterfaceinterface VarChangeInterface{Integer change(String str);}/*** 测试引用变量*/private void  testReferenceVar(){ArrayList<String> list = new ArrayList<>();list.add("111");//访问外部引用局部引用变量VarChangeInterface varChangeInterface = ((str) -> Integer.valueOf(list.get(0)));//修改局部引用变量list.set(0,"222");Integer str =varChangeInterface.change("");System.out.println(str);}/*** 测试静态变量*/void testStaticVar(){staticVar="222";VarChangeInterface varChangeInterface = (str -> Integer.valueOf(str+staticVar));staticVar="333";Integer str =varChangeInterface.change("111") ;System.out.println(str);}/*** 测试实例变量*/void testInstanceVar(){instanceVar="222";VarChangeInterface varChangeInterface = (str -> Integer.valueOf(str+instanceVar));instanceVar="333";Integer str =varChangeInterface.change("111") ;System.out.println(str);}public static void main(String[] args) {new LambdaScopeTest().testReferenceVar();new LambdaScopeTest().testStaticVar();new LambdaScopeTest().testInstanceVar();}
}

在这里插入图片描述

Lambda表达式里不允许声明一个与局部变量同名的参数或者局部变量。

//编程报错Integer tempInt = 1;VarTestInterface varTest01 = (tempInt -> Integer.valueOf(tempInt));VarTestInterface varTest02 = (str -> {Integer tempInt = 1;Integer.valueOf(str);});

8.3 Lambda表达式访问局部变量作限制的原因

Lambda表达式不能访问非final修饰的局部变量的原因是,局部变量是保存在栈帧中的。而在Java的线程模型中,栈帧中的局部变量是线程私有的,如果允许Lambda表达式访问到栈帧中的变量地址(可改变的局部变量),则会可能导致线程私有的数据被并发访问,造成线程不安全问题。

基于上述,对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。

对于基本数据类型的变量,在 Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的,因此无法知道其他线程对该变量的修改,如果该变量不做final修饰,会造成数据不同步的问题。

但是实例变量,静态变量不作限制,因为实例变量,静态变量是保存在堆中(Java8之后),而堆是线程共享的。在Lambda表达式内部是可以知道实例变量,静态变量的变化。

9. Lambda表达式的优缺点

  • 优点:
  1. 使代码更简洁,紧凑
  2. 可以使用并行流来并行处理,充分利用多核CPU的优势
    有利于JIT编译器对代码进行优化
  • 缺点:
  1. 非并行计算情况下,其计算速度没有比传统的 for 循环快
  2. 不容易调试
  3. 若其他程序员没有学过 Lambda 表达式,代码不容易看懂

在Stream操作中使用lambda表达式:Java8新特性Stream的使用总结

笔记总结自:尚硅谷的视频教程-【Java8新特性】
参考:
1.Java 8 Lambda 表达式
2.Lambda-让人又爱又恨的“->"
推荐阅读:聊聊Java 8 Lambda 表达式


http://chatgpt.dhexx.cn/article/AoC4AVsT.shtml

相关文章

SQL语句学习之SQL基础的表创建以及添加数据

SQL语句学习之SQL基础的表创建以及添加数据 学习目标1&#xff1a; 一周内掌握SQL基础语句 tip:主要是在牛客网&#xff08;牛客网&#xff09;上进行练习&#xff0c;里面有在线编程&#xff0c;可以直接运行&#xff0c;而且有解题的思路&#xff0c;比较清晰&#xff0c;而…

Hive SQL之表与建表

Hive数据模型总览 用户通过数据库访问Hive后&#xff0c;首先选择哪个数据库&#xff0c;然后在库的下面选择的是一张张表&#xff0c;表是管理数据的的最基本的所在&#xff0c;在表中的一行行记录&#xff0c;在现实中就是一条条数据&#xff0c;里面有我们的字段字段类型和它…

SQLite 创建表SQL语句

SQLite 创建表 创表语法 CREATE TABLE [表名称](--主键列不可为空[列1] [类型] PRIMARY KEY NOT NULL,--列可为空[列2] [类型],--列不可为空[列3] [类型] NOT NULL );创表示例 CREATE TABLE User (Id INT PRIMARY KEY NOT NULL,Name Text,Sex INT NOT NULL )在线Sqlite查看器…

SQL创建表为啥不显示

这里新建表 左上角报存 保存完以后点击刷新就会出来刚创建的表格

SQL 创建表的备份

1. SELECT INTO 语句 SELECT INTO 语句从一个表中选取数据&#xff0c;然后把数据插入另一个表中。 SELECT INTO 语句常用于创建表的备份复件或者用于对记录进行存档&#xff1b; SQL SELECT INTO 语法 您可以把所有的列插入新表&#xff1a; SELECT * INTO new_table_name…

SQL Server创建表

我们要怎么在数据库中创建表呢&#xff01;首先&#xff0c;表在数据库和模式中唯一命名。每个表包含一个或多个列。 每列都有一个相关的数据类型&#xff0c;用于定义它可以存储的数据类型&#xff0c;例如&#xff1a;数字&#xff0c;字符串和日期。 要创建新表&#xff0c;…

SQL Server 创建表

我们在上一节中完成了数据库的创建&#xff0c;在本节&#xff0c;我们要往这个新的数据库中加入点数据&#xff0c;要想将数据添加到数据库&#xff0c;我们就必须在数据库中添加一个表&#xff0c;接下来来看看具体的操作。 我们的数据库是一个任务跟踪数据库&#xff0c;那…

ORACLE SQL 创建表

1.创建表&#xff1a; 1.1表名和列名&#xff1a; 一定要以字母开头 一定在 1-30 个字符之间 只能包含 A–Z, a–z, 0–9, _, $, 和 # 一定不能和用户定义的其他对象重名 一定不能是Oracle 的保留字 一定要有CREATE TABLE权限 而且需要一定的存储空间 还要指定的&…

利用SQL创建表结构

一、创建图书管理系统&#xff0c;其中涉及到的对象有&#xff08;图书分类&#xff0c;图书&#xff0c;学生&#xff0c;借书记录&#xff09; 1、列出关系模式 (1) 书本类别&#xff08;种类编号&#xff0c;种类名称&#xff09; (2) 学生&#xff08;学生编号&#xff0…

SQL创建表

要创建新的表&#xff0c;就要使用create table语句。 1、第一&#xff0c;要指定数据库的名称&#xff0c;必须是数据库有的&#xff0c;如果没有指定&#xff0c;那就默认是当前数据库。&#xff08;如图下所示是没有指定数据库的&#xff09; 2、第二&#xff0c;指定表的模…

SQL表的创建

一&#xff0c;创建表 1.使用普通方法创建表 1&#xff0c;进入SQL进行连接 2&#xff0c;在左边会有一个对象资源管理器&#xff0c;右键数据库&#xff0c;在弹出的窗口中选择新建数据库 3&#xff0c;给这个包取个名字&#xff0c;在这个界面可以给这个表选择存储地方&…

SQL语句之表的创建和使用

表 一、表的创建&#xff08;DDL&#xff09;1.建表的语法格式创建一个学生表 2.mysql中的数据类型3.删除表 二、在表中插入数据insert&#xff08;DML&#xff09;1.insert2.insert插入日期3.date和datetime区别 三、修改(update)DML1.语法格式 四、删除数据(delete)DML1.语法…

SQL 创建数据库,创建表

1.SQL CREATE DATABASE 语法 CREATE DATABASE 库名;创建数据库后&#xff0c;您可以在数据库列表中检查它。 SHOW DATABASES;2.SQL CREATE TABLE 语句 CREATE TABLE 语句用于创建数据库中的表。表由行和列组成&#xff0c;每个表都必须有个表名。 SQL CREATE TABLE 语法 C…

Autojs-识别验证码-超级鹰打码(全网独一份,求个三连不过分吧)

Autojs-识别验证码-超级鹰打码 前言先看效果再放代码封装一下1.识别图片2.报错返分3.查询用户的题分信息4.主函数 超级鹰文档错误码验证码类型&#xff08;截了几个常用的&#xff09;1.英文数字2.中文3.纯英文4.纯数字 最后求个三连~ 前言 JavaScript语言的超级鹰打码应该是全…

国内十大无代码平台,无代码app开发平台有哪些?

相信大家都知道&#xff0c;无代码开发就是软件开发者无需通过手工编码就可以达到目标需求的一种软件开发方式&#xff0c;降低了程序员对重复功能代码编写的任务量&#xff0c;使应用软件的开发效率得到了大幅提升&#xff0c;所以就备受欢迎&#xff0c;那么市面上哪些无代码…

html页面自动登录,抓取网页时自动登录和自动输入验证码

集搜客GooSeeker网页抓取软件可以与在线打码平台对接,如果抓取的网站要求输入验证码,那么就把验证码转发给在线打码平台,GooSeeker将打码平台给回的结果自动录入到网页上,完成打码过程。集搜客GooSeeker V5.1.0版本支持如下功能与联众打码平台对接,请用户自行在联众打码平…

测试-小程序打码平台

一、背景 1、小程序不同于H5有线上和线下环境&#xff0c;而是区分开发版、体验版、正式版&#xff0c;并且每个版本都有对应的权限管控 2、平时项目测试过程中&#xff0c;都是基于开发码进行测试和验收的&#xff1b; 3、开发码生成的流程&#xff1a;开发分支代码本地编译打…

php对接打码平台,易语言接入打码平台的方法

易语言作为一个可视化操作的中文编程软件&#xff0c;可以很更利的开发一些营销类的软件&#xff0c;能做自动化工作&#xff0c;这时对于有输入验证码的操作的&#xff0c;就要接入到打码平台了&#xff0c;现在看看易语言怎样接入到打码平台的&#xff01; 1、对于打码平台&a…

autojs-识别验证码-联众打码

联众官网进不去了 新写了超级鹰打码 需要的可以看看&#xff1a;https://blog.csdn.net/tfnmdmx/article/details/123329446 本篇保姆级教程&#xff0c;包括账号注册&#xff0c;获取点数&#xff0c;上传图片获得结果&#xff0c;结果报错以及返回用户点数函数&#xff01;…

两阶段(two stage)目标检测原理详解 -- RCNN

目录 一、目标检测的任务 二、什么是“两阶段”呢&#xff1f; 三、两阶段算法原理 &#xff08;二&#xff09;候选区域算法 &#xff08;三&#xff09;边框回归&#xff08; Bounding Box Regression &#xff09; 1. 非极大值抑制&#xff08;Non-Maximum Suppressio…