Lambda表达式

本文深入介绍了Java 8的Lambda表达式,包括其简化匿名内部类的使用、标准格式、与方法的对比,以及在无参数无返回值和有参数有返回值场景下的应用。通过示例展示了Lambda如何实现线程任务和对象排序,并探讨了其背后的实现原理。此外,还讨论了Lambda与匿名内部类的区别,强调了在接口只有一个抽象方法时使用Lambda的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lambda表达式介绍

使用匿名内部类存在的问题,当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法,代码如下:

package com.ll.lambda;

/**
 * @Author 奥特曼
 * @Date 2022/8/17 0017 21:26
 * @Description TODO
 **/
public class LambdaDemo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程任务执行!");
            }
        }).start();
        new Thread(() -> System.out.println("lambda线程任务执行!")).start();
    }
    public void run() {
        System.out.println("aa");
    }

}


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

Lambda体验

Lambda是一个匿名函数,可以理解为一段可以传递的代码。
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单Lambda表达式达到相同的效果。

在这里插入图片描述

这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。

Lambda的优点

简化匿名内部类的使用,语法更加简单。

Lambda的标准格式

Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:
在这里插入图片描述
格式说明:
(参数类型 参数名称):参数列表
{代码体;}:方法体
-> :箭头,分隔参数列表和方法体

Lambda与方法的对比

匿名内部类中

public void run() {
System.out.println("aa");
}

Lambda中

() -> System.out.println("bb!")

练习无参数无返回值的Lambda

package com.ll.lambda;

public interface Swimmable {
    public abstract void swimming();
}


package com.ll.lambda;

/**
 * @Author 奥特曼
 * @Date 2022/8/17 0017 21:32
 * @Description TODO
 **/
public class LambdaDemo1 {
    public static void main(String[] args) {
        goSwimming(new Swimmable() {
            @Override
            public void swimming() {
                System.out.println("匿名内部类游泳");
            }
        });
        goSwimming(() -> {
            System.out.println("Lambda游泳");
        });
    }

    public static void goSwimming(Swimmable swimmable) {
        swimmable.swimming();
    }
}

练习有参数有返回值的Lambda

下面举例演示 java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
传统写法
如果使用传统的代码对 ArrayList 集合进行排序,写法如下:

package com.ll.lambda;

/**
 * @Author 奥特曼
 * @Date 2022/8/17 0017 21:34
 * @Description TODO
 **/
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

}
package com.ll.lambda;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * @Author 奥特曼
 * @Date 2022/8/17 0017 21:37
 * @Description TODO
 **/

public class LambdaDemo2 {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华", 58));
        persons.add(new Person("张学友", 58));
        persons.add(new Person("刘德华", 54));
        persons.add(new Person("黎明", 53));
        Collections.sort(persons, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        for (Person person : persons) {
            System.out.println(person.getName()+": "+person.getAge());
        }
    }
}

这种做法在面向对象的思想中,似乎也是“理所当然”的。其中 Comparator 接口的实例(使用了匿名内部类)代表“按照年龄从小到大”的排序规则。
Lambda写法:

package com.ll.lambda;

import java.util.*;
import java.util.function.Consumer;

/**
 * @Author 奥特曼
 * @Date 2022/8/17 0017 21:37
 * @Description TODO
 **/

public class LambdaDemo2 {
    public static void main(String[] args) {
        ArrayList<Person> persons = new ArrayList<>();
        persons.add(new Person("刘德华", 58));
        persons.add(new Person("张学友", 58));
        persons.add(new Person("刘德华", 54));
        persons.add(new Person("黎明", 53));
        Collections.sort(persons, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        for (Person person : persons) {
            System.out.println(person.getName() + ": " + person.getAge());
        }
        System.out.println("-----------------");
        List<Integer> list = Arrays.asList(11, 22, 33, 44);
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
        System.out.println("-----------------");
        list.forEach((s) -> {
            System.out.println(s);
        });
    }
}

以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重写。

Lambda的实现原理

匿名内部类会在编译后产生一个类.
lambda运行程序,并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一个新的类。
使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。
在DOS命令行输入:
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员

lambda类中多出了一个私有的静态方法 lambda$main 0 可以确认 l a m b d a 0可以确认 lambda 0可以确认lambdamain 0 里面放的就是 L a m b d a 中的内容,我们可以这么理解 l a m b d a 0 里面放的就是Lambda中的内容,我们可以这么理解 lambda 0里面放的就是Lambda中的内容,我们可以这么理解lambdamain$0 方法:

关于这个方法 lambda$main 0 的命名:以 l a m b d a 开头,因为是在 m a i n ( ) 函数里使用了 l a m b d a 表达式,所以带有 0 的命名:以lambda开头,因为是在main()函数里使用了lambda表达式,所以带有 0的命名:以lambda开头,因为是在main()函数里使用了lambda表达式,所以带有main表示,因为是第一个,所以$0。如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,
可以在运行时加上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文件中。使用java命令如下:

java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名

小结

匿名内部类在编译的时候会形成一个class文件。
Lambda在程序运行的时候形成一个类。

  1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码。
  2. 还会形成一个匿名内部类,实现接口,重写抽象方法。
  3. 在接口的重写方法中会调用新生成的方法.。

Lambda省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> {
return new Person();
}

省略后
a -> new Person()

Lambda的前提条件

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

函数式接口

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

Lambda和匿名内部类对比

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

小结

当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值