之前写过一篇文章 Java中的Lambda是如何实现的,该篇文章中讲到,在lambda表达式执行时,jvm会先为该lambda生成一个java类,然后再创建一个该类对应的对象,最后执行该对象对应的方法,以此来执行我们写的lambda方法体。
那该lambda表达式每次执行时都会创建一个新对象吗?
我们先卖个关子,不说答案,先看几个例子:
$ cat Test.java
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
test(i);
}
}
static void test(int a) {
forEach(b -> check(a + b));
}
static void forEach(Consumer<Integer> c) {
System.out.println(c);
c.accept(1);
}
static void check(int i) {
if (i < 0) {
throw new RuntimeException();
}
}
}
$ java Test.java
Test$$Lambda$216/0x0000000800c93c40@f0f2775
Test$$Lambda$216/0x0000000800c93c40@5a4aa2f2
Test$$Lambda$216/0x0000000800c93c40@6591f517
由上可见,我们在调用forEach方法时,传入的参数是一个lambda表达式,forEach方法在执行前,会输出一下这个lambda表达式对应的对象。
通过上面的输出结果我们发现,三次输出的lambda表达式对应的对象的值均不同,由此可知,每次调用forEach方法时,都新建了一个该lambda表达式对应的对象。
我们再来看另外一个例子:
$ cat Test.java
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
test(i);
}
}
static void test(int a) {
forEach(Test::check); // 等同于forEach(b -> check(b))
}
static void forEach(Consumer<Integer> c) {
System.out.println(c);
c.accept(1);
}
static void check(int i) {
if (i < 0) {
throw new RuntimeException();
}
}
}
$ java Test.java
Test$$Lambda$221/0x0000000800c94040@709ba3fb
Test$$Lambda$221/0x0000000800c94040@709ba3fb
Test$$Lambda$221/0x0000000800c94040@709ba3fb
这次这个例子和上个例子的区别是,传入forEach方法的lambda表达式里,没有再使用test方法的参数a,执行该示例后我们发现,三次输出的lambda表达式的对象结果都是一样的,这说明三次forEach执行使用都是同一个lambda对象。
也就是说,如果lambda表达式里使用了上下文中的其他变量,则每次lambda表达式的执行,都会创建一个新对象,而如果lambda表达式里没有使用上下文中的其他变量,则每次lambda的执行,都共用同一个对象,对吗?
在初次执行上面的两个示例后,看到执行结果,我就是这么猜测的,而在又一遍看过jvm中lambda相关实现代码后,也验证了我这个猜测是对的。
关键代码是下面这个方法:
// java.lang.invoke.InnerClassLambdaMetafactory
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
if (invokedType.parameterCount() == 0 && !disableEagerInitialization) {
// In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance,
// unless we've suppressed eager initialization
final Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
Object inst = ctrs[0].newInstance();
return new ConstantCallSite(MethodHandles.constant(samBase, inst));
} else {
return new ConstantCallSite(
MethodHandles.Lookup.IMPL_LOOKUP
.findStatic(innerClass, NAME_FACTORY, invokedType));
}
}
为了方便理解,我对该方法做了精简。
在该方法中,先调用spinInnerClass方法,为该lambda表达式生成一个java类,然后判断该lambda表达式有没有使用上下文中的其他变量,如果没有(invokedType.parameterCount() == 0),则直接创建一个该类的实例,并在以后每次执行该lambda表达式时,都使用这个实例。
如果使用了上下文中的其他变量,则每次执行lambda表达式时,都会调用innerClass里的一个名为NAME_FACTORY(get$Lambda)的静态方法,该方法会新建一个新的lambda实例。该过程在上一篇文章中有讲,这里就不再赘述了。
综上可知:
当lambda表达式里没有使用上下文中的其他变量时,则每次执行lambda表达式都使用同一个对象。
当lambda表达式里使用了上下文中的其他变量时,则每次执行lambda表达式都会新建一个对象。
本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!