Lambda表达式超详细总结

Lambda表达式超详细总结在 2014 年 Oracle 发布了 Java8 在里面增加了 Lambda 模块 于是 Java 程序员们又多了一种新的编程方式 函数式编程

1. 什么是Lambda表达式

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

2. 为什么使用Lambda表达式

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

一个简单示例:

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

public class LambdaDemo { 
    / * 成员内部类 */ class MyThread01 implements Runnable{ 
    @Override public void run() { 
    System.out.println("成员内部类:用Lambda语法创建线程吧!"); } } / * 静态内部类 */ static class MyThread02 implements Runnable{ 
    @Override public void run() { 
    System.out.println("静态内部类:对啊,用Lambda语法创建线程吧!"); } } public static void main(String[] args) { 
    / * 局部内部类 */ class MyThread03 implements Runnable{ 
    @Override public void run() { 
    System.out.println("局部内部类:用Lambda语法创建线程吧!"); } } / * 匿名内部类 */ Runnable runnable = new Runnable(){ 
    @Override public 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体只需一条语句。如下:

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

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

 @Test public 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体中有多条语句。

 @Test public 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和大括号都可以省略不写

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

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

 @Test public 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 什么是函数式接口

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内置函数式接口

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

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

 / * 产生指定的整数集合放到集合中 * Iterable接口的forEach方法的定义:方法中使用到了Consumer消费型接口, * default void forEach(Consumer 
    action) { * Objects.requireNonNull(action); * for (T t : this) { * action.accept(t); * } * } */ @Test public 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

:函数型接口 R apply(T t)

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

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

 / * 自定义条件过滤字符串集合 */ @Test public 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表达式的另外一种表现形式。
方法引用的语法:使用操作符“::”将对象或类和方法名分隔开。
方法引用的使用情况共分为以下三种:

  • 对象::实例方法名
  • 类::静态方法名
  • 类::实例方法名
 / *PrintStream中的println方法定义 * public void println(String x) { * synchronized (this) { * print(x); * newLine(); * } * } */ //对象::实例方法名 @Test public 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); * } */ @Test public void test2(){ 
    Comparator<Integer> comparable=(x,y)->Integer.compare(x,y); //使用方法引用实现相同效果 Comparator<Integer> integerComparable=Integer::compare; System.out.println(integerComparable.compare(4,2));//结果:1 System.out.println(comparable.compare(4,2));//结果:1 } 

3.类::实例方法名

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

6. 构造器引用

public class Employee { 
    private Integer id; private String name; private Integer age; @Override public 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; } } 

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

 @Test public 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. 数组引用

 @Test public 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 static String instanceVar; @FunctionalInterface interface 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 表达式



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211746.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午9:42
下一篇 2026年3月18日 下午9:42


相关推荐

  • 计算机系统 流水线技术

    计算机系统 流水线技术Python 微信订餐小程序课程视频 https edu csdn net course detail 36074Python 实战量化交易理财系统 https edu csdn net course detail 35475 计算机系统流水线技术 Mar31 2022 nbsp 字数统计 3 3k 字 nbsp nbsp 阅读时长 11 分钟 本文介绍计算机系统中采用的流水线技术 包括流水线相关的基础知识 工作原理 流水线技术对性能的改进 吞吐率 加速比和效率等相关的计算以

    2026年3月16日
    1
  • Centos 6安装完美搭建mysql、php、apache之旅

    Centos 6安装完美搭建mysql、php、apache之旅

    2022年1月20日
    43
  • Java 1.8 API 帮助文档-中文版

    Java 1.8 API 帮助文档-中文版百度云链接 https pan baidu com s 1mE O6biq80Z bCO ROOWug 密码 m41r

    2026年3月16日
    1
  • [机器学习]回归–Support Vector Regression(SVR)

    [机器学习]回归–Support Vector Regression(SVR)SVM 分类 就是找到一个平面 让两个分类集合的支持向量或者所有的数据 LSSVM 离分类平面最远 SVR 回归 就是找到一个回归平面 让一个集合的所有数据到该平面的距离最近 SVR 是支持向量回归 supportvecto 的英文缩写 是支持向量机 SVM 的重要的应用分支 传统回归方法当且仅当回归 f x 完全等于 y 时才认为预测正确 如线性回归中常用 f x

    2026年3月26日
    2
  • 网关地址和网关IP是什么[通俗易懂]

    网关地址和网关IP是什么[通俗易懂]一、什么是IP?:1、IP地址是指互联网协议地址(英语:InternetProtocolAddress,又译为网际协议地址),是IPAddress的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。目前还有些ip代理软件,但大部分都收费。2、IP是英文InternetProtocol的缩写,意思是“网络之间…

    2022年6月16日
    34
  • MyBatis Log Plugin激活码买(注册激活)2022.02.23

    (MyBatis Log Plugin激活码买)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

    2022年4月1日
    99

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号