前言:
眼前朋友们对“c调用java类”大致比较注重,看官们都需要学习一些“c调用java类”的相关内容。那么小编同时在网摘上收集了一些对于“c调用java类””的相关知识,希望我们能喜欢,咱们一起来了解一下吧!一、介绍
Java Lambda表达式是Java 8中引入的新特性,它是一种简洁而又强大的闭包语法。Lambda表达式可以替代匿名类,并且更加易读和简洁。以下是有关Java Lambda表达式的详细信息:
Lambda表达式的语法
Lambda表达式的语法如下:
(parameters) -> expression
或者
(parameters) -> { statements; }
其中,parameters表示Lambda表达式的参数列表,可以是空的或包含一个或多个参数。箭头符号->用于分隔参数列表和Lambda表达式的主体。如果Lambda表达式主体只有一条语句,则可以使用第一种语法,否则需要使用第二种语法,并使用大括号括起来。
Lambda表达式的类型
Lambda表达式本质上是一个函数,因此它具有类型。Lambda表达式的类型由两部分组成:参数类型和返回类型。这些类型可以根据Lambda表达式主体中的代码自动推断,因此通常不需要显式指定。
Lambda表达式的应用
Lambda表达式可以用在Java中支持函数式接口的任何场景中,例如:
集合框架中的过滤器和映射器线程和并发编程中的回调GUI编程中的事件处理程序Web应用程序中的路由处理程序等
Lambda表达式的优点
Lambda表达式相对于匿名类具有以下优点:
更加简洁:Lambda表达式通常比匿名类更短,更易读。更加直观:Lambda表达式可以使代码更加直观,易于理解。更加高效:Lambda表达式不需要创建新的类或实例,因此在某些情况下可以提高性能。
综上所述,Java Lambda表达式是一种非常强大和实用的语言特性,它可以帮助我们更轻松地编写函数式代码。
二、例子
以下是一个使用Lambda表达式的实际例子,该例子使用Lambda表达式过滤列表中的元素:
import java.util.ArrayList;import java.util.List;import java.util.stream.Collectors;public class Main { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); names.add("Charlie"); names.add("Dave"); names.add("Eve"); // 使用Lambda表达式过滤列表中的元素 List<String> filteredNames = names.stream() .filter(name -> name.length() > 4) .collect(Collectors.toList()); System.out.println(filteredNames); }}
在上述代码中,我们使用Lambda表达式来过滤列表中长度大于4的字符串。我们使用Java8 Stream API中的filter()方法,该方法接受一个函数式接口Predicate作为参数,并返回一个Stream对象。在这里,我们使用Lambda表达式作为Predicate的实现,并检查每个字符串的长度是否大于4。
然后,我们使用collect()方法将Stream对象转换为List对象,并打印过滤后的结果。这个例子演示了Lambda表达式的实际应用,它使得代码更加简洁、直观、易懂。
三、原理
经过上面的介绍,我们看到 Lambda 表达式只是为了简化匿名内部类书写,看起来似乎在编译阶段把所有的 Lambda 表达式替换成匿名内部类就可以了。但实际情况并非如此,在 JVM 层面,Lambda 表达式和匿名内部类其实有着明显的差别。
匿名内部类的实现
匿名内部类仍然是一个类,只是不需要我们显式指定类名,编译器会自动为该类取名。比如有如下形式的代码:
public classLamTest { publicstaticvoidmain(String[] args) { newThread(newRunnable() { @Overridepublicvoidrun() { System.out.println("lambda test"); } }).start(); }}
编译之后将会产生两个 class 文件:LamTest.class LamTest$1.class
使用 javap -c LamTest.class 进一步分析 LamTest.class 的字节码,部分结果如下:
可以发现在 4: new #3 这一行创建了匿名内部类的对象。
Lambda 表达式的实现
接下来我们将上面的示例代码使用 Lambda 表达式实现,代码如下:
public classLamTest { public static voidmain(String[] args) { newThread(()->System.out.println("lambda test")).start(); }}
此时编译后只会产生一个文件 LamTest.class,再来看看通过 javap 对该文件反编译后的结果。
从上面的结果我们发现 Lambda 表达式被封装成了主类的一个私有方法,并通过 invokedynamic 指令进行调用。
其实在设计 Lambda 表达式的解析方案时,主要考虑下面两点:
可扩展性,不把解析方案写死在字节码上;
解析 Lambda 表达式后,对字节码文件干扰尽量降到最低,起码保证一定的可读性。
正式基于以上两点考虑, invokedynamic 指令被运用到解析 Lambda 表达式,优势如下:
字节码表示简单,利用 invokedynamic 指令本身的特性(引导方法),把 Lambda 表达式在字节码文件的表示和真正的解析分离;
Lambda 表达式的链接解析发生运行期而非编译期,由 invokedynamic 的引导方法执行,扩展性强。
Lambda 是面向方法接口编程,其实我更多认为是 JDK8 提供的语法糖,因为对非方法引用的 Lambda 表达式,编译器都会为其生成一个方法实现 Lambda 表达式的逻辑,并出现在编译后的字节码文件中。这个方法的生成是 Lambda 表达式解析的关键,Lambda 表达式的解析分成三个阶段:
链接阶段, 负责生成动态调用点变量捕获,闭包中的变量访问Lambda 表达式调用
链接阶段由 invokedynamic 指令后面跟随的 Bootstrap Method 引导方法完成,生成动态调用点。我们先来看 Lambda 表达式解析用的最多的一个引导方法:
java.lang.invoke.LambdaMetafactory#metaFactory:
该启动方法以及参数都是编译器指定,下面来解释下这几个参数的含义:
caller 指的是 MethodHandle,lookupClass 指向 enclosing object
invokedName 需要实现的函数式接口方法名
invokedType 变量捕获列表
samMethodType 需要实现的函数式接口方法签名(sam 是 single abstract method )
implMethod 指向生成的 desugaring method
instantiatedMethodType 对应运行时 sam 的方法签名,运行时签名验证,在非泛型情况下,和 samMethodType 相同;比如下面代码中,samMethodType 是(Ljava/lang/Object)V, instantiatedMethodType 则是(Ljava/lang/String)V
引导方法 metaMethod 根据这些参数生成 java.lang.invoke.CallSite 动态调用点对象支持 Lambda 表达式的调用执行。在引导方法中会动态生成一个模板匿名类,查看这个类的字节码可以通过加上 -Djdk.internal.lambda.dumpProxyClasses 参数指定 dump 的目录,该匿名是一个模板类,其特点如下:
实现函数式接口,内部逻辑很简单,调用上面提到的脱糖方法(desugaring method);
生成一个构造方法, 构造方法参数为被捕获参数变量,所有变量存储为类实例变量;
如果被捕获参数变量列表不为空, 则会生成一个工厂方法 get$lambad,方法签名和构造方法相同;该工厂方法的作用是避免重复调用 asm 生成匿名类字节码,提升性能;在下一节的性能分析中会提到这个方法的作用。
因此,我们可以得出结论:Lambda 表达式是通过 invokedynamic 指令实现的,并且书写 Lambda 表达式不会产生新的类。
标签: #c调用java类