下面是两组通过 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() {
}
}
最佳答案
此代码重现了您的发现:
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$1()V
--------------------------------------
L0:
{
NEW
DUP
INVOKESPECIAL java/lang/Object/<init>()V
INVOKEVIRTUAL java/lang/Object/toString()Ljava/lang/String;
POP
RETURN
}
METHOD: lambda$main$0(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$1", 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$2", 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());
}
在捕获变量的情况下,每次都会返回一个新的Runnable
(如果无法优化调用点,它可以修改方法句柄),并且在函数体保持不变(即:不捕获),每次都会返回相同的 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 - 创建的 Runnables 的 lambda 的不同行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62716090/