创建的 Runnable 的 lambda 的不同行为
Different behavior of lambdas for Runnables created
以下是通过 Java 8 个 lambda 表达式创建多个可运行对象的两组代码。但是,我发现对象创建的行为有所不同。
代码 1 - 创建多个对象,因为哈希码不同
PasswordUtils passwordUtils = new PasswordUtils();
for(int i = 0 ; i < 100 ; i++) {
Runnable r = () -> passwordUtils.print(); //difference
System.out.println(r.hashCode());
}
输出-
1129670968
1023714065
.. //varies in each iteration
代码 2 - 创建单个对象,因为所有对象的哈希码都相同。
for(int i = 0 ; i < 100 ; i++) {
Runnable r = () -> new PasswordUtils().print(); //difference
System.out.println(r.hashCode());
}
输出-
1254526270
1254526270
... // same on all iterations
区别在于创建 PasswordUtils 对象的时间。造成这种差异的原因是什么?
编辑: 只是为了完成代码,可以使用
class PasswordUtils {
void print() {
}
}
基于 Does a lambda expression create an object on the heap every time it's executed?
的评论
... For stateless lambdas (those that do not capture anything from their lexical context), only one instance will ever be created (lazily), and cached at the capture site. (This is how the implementation works; the spec was carefully written to allow, but not require, this approach.)
在代码 1 中创建了一个 PasswordUtil,然后在 lambda 中使用,因此它确实 从词法上下文中捕获了一些东西。在代码 2 中,PasswordUtil 是在 lambda 内部创建的,而 lambda 在创建时从不从其上下文中捕获任何内容,因此只创建了一个实例。因此输出相同的hashCode。
answer to the linked question 提供了有关在 JLS 中何处可以找到此条件的更多信息。我在此处添加了一个答案,以说明为什么这个特定案例是未创建新实例的示例。
重现您的发现的代码:
public class Main {
public static void main(String[] args) throws IOException {
Object passwordUtils = new Object();
for(int i = 0 ; i < 10 ; i++) {
Runnable r = () -> passwordUtils.toString();
System.out.println(r.hashCode());
}
System.out.println("-------");
for(int i = 0 ; i < 10; i++) {
Runnable r = () -> new Object().toString();
System.out.println(r.hashCode());
}
}
}
编译为:
METHOD: main([Ljava/lang/String;)V
--------------------------------------
L0:
{
NEW
DUP
INVOKESPECIAL java/lang/Object/<init>()V
ASTORE_1
//int I = 0
ICONST_0
ISTORE_2
}
L2:
{
ILOAD_2
BIPUSH 10
IF_ICMPGE L3
}
L4:
{
ALOAD_1
INVOKEDYNAMIC java/lang/invoke/LambdaMetafactory/metafactory (MethodHandles$Lookup, String, MethodType, MethodType, MethodHandle, MethodType)CallSite; : run(Object)Runnable;
ASTORE_3
}
L5:
{
GETSTATIC java/lang/System/out Ljava/io/PrintStream;
ALOAD_3
INVOKEVIRTUAL java/lang/Object/hashCode()I
INVOKEVIRTUAL java/io/PrintStream/println(I)V
}
L6:
{
IINC
GOTO L2
}
L3:
{
GETSTATIC java/lang/System/out Ljava/io/PrintStream;
LDC "-------"
INVOKEVIRTUAL java/io/PrintStream/println(Ljava/lang/String;)V
}
L7:
{
ICONST_0
ISTORE_2
}
L8:
{
ILOAD_2
BIPUSH 10
IF_ICMPGE L9
}
L10:
{
INVOKEDYNAMIC java/lang/invoke/LambdaMetafactory/metafactory (MethodHandles$Lookup, String, MethodType, MethodType, MethodHandle, MethodType)CallSite; : run()Runnable;
ASTORE_3
}
L11:
{
GETSTATIC java/lang/System/out Ljava/io/PrintStream;
ALOAD_3
INVOKEVIRTUAL java/lang/Object/hashCode()I
INVOKEVIRTUAL java/io/PrintStream/println(I)V
}
L12:
{
IINC
GOTO L8
}
L9:
{
RETURN
}
METHOD: lambda$main()V
--------------------------------------
L0:
{
NEW
DUP
INVOKESPECIAL java/lang/Object/<init>()V
INVOKEVIRTUAL java/lang/Object/toString()Ljava/lang/String;
POP
RETURN
}
METHOD: lambda$main[=11=](Ljava/lang/Object;)V
--------------------------------------
L0:
{
ALOAD_0
INVOKEVIRTUAL java/lang/Object/toString()Ljava/lang/String;
POP
RETURN
}
注意第一个 InvokeDynamic
指令有一个 ALOAD_1
而另一个没有.. ALOAD_1
是捕获的对象 passwordUtils
变量..
上面的 InvokeDynamic
指令转换为:
//Calls Runnable.run with a captured variable `passwordUtils` -> #1
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(
lookup,
"run",
MethodType.methodType(Runnable.class, Object.class),
MethodType.methodType(void.class),
lookup.findStatic(Main.class, "Main$lambda", MethodType.methodType(void.class, Object.class)),
MethodType.methodType(void.class)
);
MethodHandle factory = site.getTarget();
Runnable r = (Runnable)factory.invoke(new Object());
//r.run();
System.out.println(r.hashCode());
//Calls Runnable.run() -> #2
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(
lookup,
"run",
MethodType.methodType(Runnable.class),
MethodType.methodType(void.class),
lookup.findStatic(Main.class, "Main$lambda", MethodType.methodType(void.class)), //doesn't capture
MethodType.methodType(void.class)
);
MethodHandle factory = site.getTarget();
Runnable r = (Runnable)factory.invoke();
//r.run();
System.out.println(r.hashCode());
如果将每个 #1 和 #2 封装在 for 循环中并 运行 它们,每次都会重新创建 CallSite
对象,因此会产生不同的哈希码..
但是,如果您像 JVM 那样对其进行优化,您会注意到一个调用站点被缓存而另一个未被缓存..
IE:
for (int i = 0; i < 10; ++i)
{
Runnable r = (Runnable)factory.invoke();
System.out.println(r.hashCode());
}
在捕获变量的情况下,每次都返回一个NEW Runnable
(如果不能优化callsite,可以修改方法句柄),而在body函数保持不变(即:不捕获),每次返回相同的 Runnable
(因为它在内部调用 InvokeExact
)。
/**
* Invokes the method handle, allowing any caller type descriptor,
* and optionally performing conversions on arguments and return values.
* <p>
* If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
* the call proceeds as if by {@link #invokeExact invokeExact}.
* <p>
* Otherwise, the call proceeds as if this method handle were first
* adjusted by calling {@link #asType asType} to adjust this method handle
* to the required type, and then the call proceeds as if by
* {@link #invokeExact invokeExact} on the adjusted method handle.
* <p>
* There is no guarantee that the {@code asType} call is actually made.
* If the JVM can predict the results of making the call, it may perform
* adaptations directly on the caller's arguments,
* and call the target method handle according to its own exact type.
* <p>
* The resolved type descriptor at the call site of {@code invoke} must
* be a valid argument to the receivers {@code asType} method.
* In particular, the caller must specify the same argument arity
* as the callee's type,
* if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
* <p>
* When this method is observed via the Core Reflection API,
* it will appear as a single native method, taking an object array and returning an object.
* If this native method is invoked directly via
* {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
* or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
* it will throw an {@code UnsupportedOperationException}.
* @param args the signature-polymorphic parameter list, statically represented using varargs
* @return the signature-polymorphic result, statically represented using {@code Object}
* @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
* @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
* @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
*/
public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
以下是通过 Java 8 个 lambda 表达式创建多个可运行对象的两组代码。但是,我发现对象创建的行为有所不同。
代码 1 - 创建多个对象,因为哈希码不同
PasswordUtils passwordUtils = new PasswordUtils();
for(int i = 0 ; i < 100 ; i++) {
Runnable r = () -> passwordUtils.print(); //difference
System.out.println(r.hashCode());
}
输出-
1129670968
1023714065
.. //varies in each iteration
代码 2 - 创建单个对象,因为所有对象的哈希码都相同。
for(int i = 0 ; i < 100 ; i++) {
Runnable r = () -> new PasswordUtils().print(); //difference
System.out.println(r.hashCode());
}
输出-
1254526270
1254526270
... // same on all iterations
区别在于创建 PasswordUtils 对象的时间。造成这种差异的原因是什么?
编辑: 只是为了完成代码,可以使用
class PasswordUtils {
void print() {
}
}
基于 Does a lambda expression create an object on the heap every time it's executed?
的评论... For stateless lambdas (those that do not capture anything from their lexical context), only one instance will ever be created (lazily), and cached at the capture site. (This is how the implementation works; the spec was carefully written to allow, but not require, this approach.)
在代码 1 中创建了一个 PasswordUtil,然后在 lambda 中使用,因此它确实 从词法上下文中捕获了一些东西。在代码 2 中,PasswordUtil 是在 lambda 内部创建的,而 lambda 在创建时从不从其上下文中捕获任何内容,因此只创建了一个实例。因此输出相同的hashCode。
answer to the linked question 提供了有关在 JLS 中何处可以找到此条件的更多信息。我在此处添加了一个答案,以说明为什么这个特定案例是未创建新实例的示例。
重现您的发现的代码:
public class Main {
public static void main(String[] args) throws IOException {
Object passwordUtils = new Object();
for(int i = 0 ; i < 10 ; i++) {
Runnable r = () -> passwordUtils.toString();
System.out.println(r.hashCode());
}
System.out.println("-------");
for(int i = 0 ; i < 10; i++) {
Runnable r = () -> new Object().toString();
System.out.println(r.hashCode());
}
}
}
编译为:
METHOD: main([Ljava/lang/String;)V
--------------------------------------
L0:
{
NEW
DUP
INVOKESPECIAL java/lang/Object/<init>()V
ASTORE_1
//int I = 0
ICONST_0
ISTORE_2
}
L2:
{
ILOAD_2
BIPUSH 10
IF_ICMPGE L3
}
L4:
{
ALOAD_1
INVOKEDYNAMIC java/lang/invoke/LambdaMetafactory/metafactory (MethodHandles$Lookup, String, MethodType, MethodType, MethodHandle, MethodType)CallSite; : run(Object)Runnable;
ASTORE_3
}
L5:
{
GETSTATIC java/lang/System/out Ljava/io/PrintStream;
ALOAD_3
INVOKEVIRTUAL java/lang/Object/hashCode()I
INVOKEVIRTUAL java/io/PrintStream/println(I)V
}
L6:
{
IINC
GOTO L2
}
L3:
{
GETSTATIC java/lang/System/out Ljava/io/PrintStream;
LDC "-------"
INVOKEVIRTUAL java/io/PrintStream/println(Ljava/lang/String;)V
}
L7:
{
ICONST_0
ISTORE_2
}
L8:
{
ILOAD_2
BIPUSH 10
IF_ICMPGE L9
}
L10:
{
INVOKEDYNAMIC java/lang/invoke/LambdaMetafactory/metafactory (MethodHandles$Lookup, String, MethodType, MethodType, MethodHandle, MethodType)CallSite; : run()Runnable;
ASTORE_3
}
L11:
{
GETSTATIC java/lang/System/out Ljava/io/PrintStream;
ALOAD_3
INVOKEVIRTUAL java/lang/Object/hashCode()I
INVOKEVIRTUAL java/io/PrintStream/println(I)V
}
L12:
{
IINC
GOTO L8
}
L9:
{
RETURN
}
METHOD: lambda$main()V
--------------------------------------
L0:
{
NEW
DUP
INVOKESPECIAL java/lang/Object/<init>()V
INVOKEVIRTUAL java/lang/Object/toString()Ljava/lang/String;
POP
RETURN
}
METHOD: lambda$main[=11=](Ljava/lang/Object;)V
--------------------------------------
L0:
{
ALOAD_0
INVOKEVIRTUAL java/lang/Object/toString()Ljava/lang/String;
POP
RETURN
}
注意第一个 InvokeDynamic
指令有一个 ALOAD_1
而另一个没有.. ALOAD_1
是捕获的对象 passwordUtils
变量..
上面的 InvokeDynamic
指令转换为:
//Calls Runnable.run with a captured variable `passwordUtils` -> #1
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(
lookup,
"run",
MethodType.methodType(Runnable.class, Object.class),
MethodType.methodType(void.class),
lookup.findStatic(Main.class, "Main$lambda", MethodType.methodType(void.class, Object.class)),
MethodType.methodType(void.class)
);
MethodHandle factory = site.getTarget();
Runnable r = (Runnable)factory.invoke(new Object());
//r.run();
System.out.println(r.hashCode());
//Calls Runnable.run() -> #2
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(
lookup,
"run",
MethodType.methodType(Runnable.class),
MethodType.methodType(void.class),
lookup.findStatic(Main.class, "Main$lambda", MethodType.methodType(void.class)), //doesn't capture
MethodType.methodType(void.class)
);
MethodHandle factory = site.getTarget();
Runnable r = (Runnable)factory.invoke();
//r.run();
System.out.println(r.hashCode());
如果将每个 #1 和 #2 封装在 for 循环中并 运行 它们,每次都会重新创建 CallSite
对象,因此会产生不同的哈希码..
但是,如果您像 JVM 那样对其进行优化,您会注意到一个调用站点被缓存而另一个未被缓存..
IE:
for (int i = 0; i < 10; ++i)
{
Runnable r = (Runnable)factory.invoke();
System.out.println(r.hashCode());
}
在捕获变量的情况下,每次都返回一个NEW Runnable
(如果不能优化callsite,可以修改方法句柄),而在body函数保持不变(即:不捕获),每次返回相同的 Runnable
(因为它在内部调用 InvokeExact
)。
/**
* Invokes the method handle, allowing any caller type descriptor,
* and optionally performing conversions on arguments and return values.
* <p>
* If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
* the call proceeds as if by {@link #invokeExact invokeExact}.
* <p>
* Otherwise, the call proceeds as if this method handle were first
* adjusted by calling {@link #asType asType} to adjust this method handle
* to the required type, and then the call proceeds as if by
* {@link #invokeExact invokeExact} on the adjusted method handle.
* <p>
* There is no guarantee that the {@code asType} call is actually made.
* If the JVM can predict the results of making the call, it may perform
* adaptations directly on the caller's arguments,
* and call the target method handle according to its own exact type.
* <p>
* The resolved type descriptor at the call site of {@code invoke} must
* be a valid argument to the receivers {@code asType} method.
* In particular, the caller must specify the same argument arity
* as the callee's type,
* if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
* <p>
* When this method is observed via the Core Reflection API,
* it will appear as a single native method, taking an object array and returning an object.
* If this native method is invoked directly via
* {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
* or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
* it will throw an {@code UnsupportedOperationException}.
* @param args the signature-polymorphic parameter list, statically represented using varargs
* @return the signature-polymorphic result, statically represented using {@code Object}
* @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
* @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
* @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
*/
public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;