十一、Lambda
是什么?
1.定义
Lambda表达式是Java 8引入的一种新语法,用于简化代码,尤其是在处理函数式接口(即只有一个抽象方法的接口)时,它允许你更简洁地表示可以作为参数传递的一段代码,比如匿名内部类的替代品。
Lambda
表达式是什么?
- 希腊字母表中排序第十一位的子母,英语名为
Lambda
C/C++
语言是有函数指针的,函数指针可以作为一个参数传给一个方法,而java
是没有这个特性的。- 为了解决这个问题,
Lambda
表达式就出来了! Lambda
表达式的核心就是函数式接口
。- 质属于函数式变成的概念。
什么是函数式接口(Functional Interface)?
函数式接口定义:任何接口,如果只包含唯一一个抽象方法,那它就是一个函数式接口!
public interface Runnable{
public abstract void run();
}
对于函数式接口,可以通过Lambda
表达式来创建改接口的对象
Runnable
接口也是函数式接口哦!
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
可以根据自己的需求实际应用到多线程中。
为什么要用?
为什么要用它?
- 避免匿名内部类定义过多
- 代码更加简洁
- 去掉了一些无意义代码,留下核心逻辑
怎么用?
1.语法
(parameters) -> expression
或
(parameters) -> { statements; }
// 无参数的Lambda表达式
Runnable r1 = () -> System.out.println("Hello, Lambda!");
// 有一个参数的Lambda表达式
Consumer<String> c1 = (s) -> System.out.println(s);
// 有多个参数的Lambda表达式
BinaryOperator<Integer> add = (a, b) -> a + b;
2.推导
不用它的时候是怎么实现的?
Lambda表达式推导代码
//1.定义一个函数式接口
interface ILikes{
void lambda();
}
//2.接口实现类
class Likes implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda!");
}
}
public class LambdaTest2 {
//3.静态内部类
static class Likes2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2!");
}
}
//4.执行main方法
public static void main(String[] args) {
//2.接口实现类
ILike like = new Likes();
like.lambda();
//3.静态内部类
like = new Likes2();
like.lambda();
//4.局部内部类
class Likes3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3!");
}
}
like = new Likes3();
like.lambda();
//5.匿名内部类,没有类的名称,必须借助接口或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4!");
}
};
like.lambda();
//6.用Lambda简化
like = () -> {
System.out.println("I like lambda5!");
};
like.lambda();
}
}
传入变量的时候怎么用?
//1.定义一个函数式接口
interface ILikes1{
void lambda(int a);
}
//2.接口实现类
class Likes1 implements ILikes1{
@Override
public void lambda(int a) {
System.out.println("I like lambda!");
}
}
public class LambdaTest3 {
public static void main(String[] args) {
//3.lambda表达式简化
ILikes1 like = new Likes1();
like = (a) -> {
System.out.println("I like lambda 1!");
};
like.lambda(11);
//4.lambda表达式再简化(注:只有一个变量且只有一行执行语句才可如此简化!)
like = a -> System.out.println("I like lambda 2!");
}
}
3.具体使用
(1).替代匿名内部类
Lambda表达式最常见的用途是替代函数式接口的匿名内部类,例如在处理集合时常用的Comparator
和Runnable
接口。
/**
* 定义了一个名为comparator的传统匿名内部类比较器,用于比较两个整数大小。
* 使用Lambda表达式定义了另一个名为filter的比较器,功能与comparator相同。
* 创建变量compare存储filter比较1和2的结果。
* 打印compare的值(-1,表示1小于2)。
*/
private static void test1(){
Comparator<Integer> comparator = new Comparator<Integer>(){
/**
* 实现Comparator接口的compare方法,用于比较两个Integer对象的大小。
*
* @param o1 第一个Integer对象,需要参与比较
* @param o2 第二个Integer对象,需要参与比较
* @return 返回两个整数比较的结果,负数表示o1小于o2,零表示两者相等,正数表示o1大于o2
*/
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
// 使用Lambda表达式创建一个Comparator比较器实例
// 该比较器用于比较两个Integer对象的大小
Comparator<Integer> filter = (o1, o2) -> o1.compareTo(o2);
// 使用比较器的compare方法比较两个整数的大小
// 这里比较的是整数1和整数2
int compare = filter.compare(1, 2);
// 打印比较的结果
// 结果为负数表示1小于2,零表示它们相等,正数表示1大于2
System.out.println(compare);
}
(2).Stream API中的使用
Lambda表达式与Stream API结合使用时非常强大,能够让你用一种声明式的风格处理集合数据。
/**
* 测试Stream API的示例方法
* 该方法展示了如何使用Stream API进行过滤操作
* 特别是演示了如何过滤出以特定字母开头的名字
*/
private static void test2() {
// 初始化一个名字列表
List<String> names = Arrays.asList("Tom", "Jerry", "Mike", "Tiger");
// 使用Stream API进行过滤和遍历
// 以下代码注释详细解释了Stream API的使用步骤
names.stream() // 转换列表为Stream,以便使用Stream API
// 过滤出以'T'开头的名字
.filter(name -> name.startsWith("T"))
//.forEach(name -> System.out.println(name)); // 以下是一种替代写法,输出每个过滤后的名字
.forEach(System.out::println);
}
(3).事件处理
在GUI编程中(如Swing),Lambda表达式也常用于简化事件处理代码。
button.addActionListener(event -> System.out.println("Button clicked!"));
4.工作应用
- 简化代码,提高可读性:在实际开发中,Lambda表达式可以极大地减少代码量,特别是在处理集合、事件、异步任务等场景下,使代码更加简洁、清晰。
- 与函数式编程结合:Java 8以后,Java逐渐引入了函数式编程的理念,Lambda表达式正是这一变化的核心部分。通过Lambda表达式,可以更容易地使用函数式接口、链式调用等功能,进一步提升代码的可维护性和可扩展性。
- 性能优化:Lambda表达式在一些场景下还能优化性能。通过使用并行流(
parallelStream()
),可以充分利用多核CPU的性能,显著提高数据处理速度。
5.总结
- Lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就要用代码块包裹。
- 前提是接口为函数式接口。
- 多个参数也可以省略参数类型,要去掉的都去掉,但必须加上括号。
底层实现
1.函数式接口和FunctionalInterface
Lambda表达式必须依赖于函数式接口。Java8引入了@FunctionalInterface
注解,用于显示声明某个接口为函数式接口。函数式接口只能包含一个抽象方法。
举例说明
@FunctionalInterface
public interface MyFunctionalInterface {
void execute();
}
2. 生成字节码
Lambda表达式在编译时并不会直接生成匿名内部类的字节码,而是通过invokedynamic
指令在运行时动态生成,这使得Lambda表达式相比匿名内部类在性能上有一定优势。
3.底层实现原理
Lambda表达式通过java.lang.invoke.MethodHandles.Lookup
类的findStatic
方法和MethodHandle
类的invokeExact
方法来绑定具体的方法引用。具体来说,Lambda表达式被编译为一个invokedynamic
指令,该指令通过java.lang.invoke.LambdaMetafactory
来动态生成所需的函数式接口实例。
// 示例:Lambda表达式编译后的字节码
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello, Lambda!");
r.run();
}
编译后的字节码中,Lambda表达式会被转换为invokedynamic
指令,而具体的执行逻辑则在运行时由LambdaMetafactory
生成。
4.核心源码
了解Java中Lambda表达式的底层实现,有助于理解其背后的高效执行机制。Java通过引入invokedynamic
指令和LambdaMetafactory
类来支持Lambda表达式。以下是核心实现过程的介绍和关键代码示例。
1. Lambda表达式的基本语法
List<String> list = Arrays.asList("a", "b", "c");
// Lambda表达式用于遍历列表
list.forEach(item -> System.out.println(item));
2. 编译器的处理
在编译过程中,Lambda表达式不会像匿名类那样被编译成单独的类文件。相反,编译器生成一个调用invokedynamic
指令的字节码,这条指令会在运行时动态解析和执行Lambda表达式。
list.forEach(item -> System.out.println(item));
这段代码经过编译后,会生成类似以下字节码指令:
invokedynamic #1:accept:(Ljava/lang/Object;)V
3. invokedynamic
和 LambdaMetafactory
-
invokedynamic
指令:invokedynamic
是Java 7引入的一种字节码指令,旨在支持动态类型语言的特性。在Java 8中,invokedynamic
被用来支持Lambda表达式的动态调用。 -
LambdaMetafactory
:在Lambda表达式的运行时,invokedynamic
指令会调用LambdaMetafactory.metafactory
方法,该方法负责生成一个调用Lambda表达式的实例。这一过程在第一次调用时完成,并且生成的实例会被缓存,以便后续调用能更高效地执行。
核心代码示例:
public class LambdaExample {
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello, Lambda!");
// 编译后,这段代码会被转换为以下方式:
// Invokedynamic指令调用LambdaMetafactory.metafactory
r.run();
}
}
4. LambdaMetafactory
工作原理
LambdaMetafactory.metafactory
的核心任务是创建一个CallSite
对象,该对象包含了Lambda表达式的目标方法句柄。在运行时,JVM会根据这个句柄创建一个函数式接口的实例(如Runnable
、Callable
等),然后执行Lambda表达式。
public class LambdaMetafactory {
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType) {
// 核心实现略
}
}
MethodHandle
:是一种对方法的引用,可以直接调用底层的方法。它比反射更加高效。CallSite
:是一个可变的动态链接点,允许在运行时绑定或重新绑定方法句柄。
5. 优化与性能
Lambda表达式的这种实现方式具有以下几个优点:
- 内存占用低:不像匿名类那样生成额外的类文件,Lambda表达式通过
invokedynamic
和LambdaMetafactory
机制在运行时动态生成。 - 执行效率高:生成的Lambda表达式实例会被缓存,避免重复生成,提升执行效率。
6.通俗表达
Lambda表达式让Java代码更简洁易读,但它背后的工作原理其实挺复杂的。下面用通俗的语言总结一下它是如何实现的:
-
写代码:当你在代码里写了一个Lambda表达式,比如
() -> System.out.println("Hello, Lambda!")
,它看起来像是一个匿名函数,可以直接使用。 -
编译阶段:当你编译代码时,Java编译器不会为每个Lambda表达式生成一个新的类文件(不像以前的匿名类)。相反,编译器会生成一个叫
invokedynamic
的指令,这个指令在代码运行时才去真正处理Lambda表达式。 -
运行时处理:
- 当程序运行到Lambda表达式这行代码时,
invokedynamic
指令会告诉JVM(Java虚拟机):“嘿,这里有个Lambda表达式,请帮我处理一下。” - JVM接到这个请求后,会调用一个特殊的工具(
LambdaMetafactory
),这个工具负责创建一个能执行Lambda表达式的“小助手”。
- 当程序运行到Lambda表达式这行代码时,
-
创建“小助手”:
LambdaMetafactory
工具会根据Lambda表达式的内容,动态生成一个对象,这个对象可以像你写的Lambda表达式那样工作。- 比如,你的Lambda表达式需要打印“Hello, Lambda!”这句话,这个“小助手”就会被设置好,让它去打印这句话。
-
执行和优化:
- 一旦“小助手”被创建,它会被缓存起来,这样以后再遇到相同的Lambda表达式,就不用再创建新的“小助手”,直接使用缓存的那个就行了,这样运行速度更快。
总结
Java通过一些复杂的机制(如invokedynamic
和LambdaMetafactory
)在运行时动态生成和执行Lambda表达式。虽然底层工作很复杂,但它让我们写代码时可以用简单的Lambda表达式来完成任务,而不需要担心性能和额外的类文件生成。这种方式既保持了代码的简洁,又确保了执行的高效。
7.总结
Java通过invokedynamic
指令和LambdaMetafactory
的动态方法调用机制,实现了Lambda表达式的高效执行。Lambda表达式在编译阶段不会生成新的类文件,而是在运行时动态创建并执行。理解这一机制,有助于Java开发者更好地利用Lambda表达式的强大功能,同时也能深入理解Java虚拟机的动态特性。
总结
Lambda表达式通过简洁的语法和强大的表达能力,使Java代码更加简洁和易读。在底层实现上,Java通过动态方法调用机制来支持Lambda表达式的高效执行。掌握Lambda表达式不仅能够提升代码质量,还能更好地应用Java 8及以上版本的现代特性。