Java基础——十一、Lambda
十一、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{@Overridepublic void lambda() {System.out.println("I like lambda!");}
}
public class LambdaTest2 {//3.静态内部类static class Likes2 implements ILike{@Overridepublic 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{@Overridepublic void lambda() {System.out.println("I like lambda3!");}}like = new Likes3();like.lambda();//5.匿名内部类,没有类的名称,必须借助接口或者父类like = new ILike() {@Overridepublic 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{@Overridepublic 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*/@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);}};// 使用Lambda表达式创建一个Comparator比较器实例// 该比较器用于比较两个Integer对象的大小Comparator<Integer> filter = (o1, o2) -> o1.compareTo(o2);// 使用比较器的compare方法比较两个整数的大小// 这里比较的是整数1和整数2int compare = filter.compare(1, 2);// 打印比较的结果// 结果为负数表示1小于2,零表示它们相等,正数表示1大于2System.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.metafactoryr.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及以上版本的现代特性。