This code that reproduces your findings:
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());
}
}
}
compiles to:
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
}
Notice that the first InvokeDynamic
instruction has an ALOAD_1
and the other doesn't.. that ALOAD_1
is the captured object passwordUtils
variable..
The InvokeDynamic
instructions above translates to:
//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());
If you encapsulated each #1 and #2 in for loops and run them, every single time it'd recreate the CallSite
object and therefore would result in different hashcodes..
However, if you were to optimize it just like the JVM would, you'd notice that one callsite is cached and the other isn't..
IE:
for (int i = 0; i < 10; ++i)
{
Runnable r = (Runnable)factory.invoke();
System.out.println(r.hashCode());
}
In the case where it captures the variable, a NEW Runnable
is returned every single time (it can modify the method handle if it can't optimize the callsite), and in the case where the body of the function stays the same (IE: no capturing), the same Runnable
is returned every time (because it calls InvokeExact
internally).
/**
* 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;