Why Java-8 lambda need invokeDynamic byteCode to invoke an interface method
The method holding the body of the lambda expression is not invoked via invokedynamic
. The implementation of the functional interface is invoked via invokeinterface
, just like any other interface method. The caller even doesn’t know whether an object implementing an interface has been generated for a lambda expression.
How this generated implementation of the interface invokes the synthetic method, is JRE specific, but usually it happens via an ordinary invocation (invokestatic
, invokevirtual
or invokespecial
). Only the access rights follow different rules, i.e. the method can be invoked by the generated class despite being private
.
The invokedynamic
instruction is used for the instantiation of the interface implementation, so it allows arbitrary, unknown implementation classes, including classes, which do not exist at compile-time, but are generated at runtime. It also opens the possibility to return an existing instance instead of creating a new one, which is impossible for ordinary instance creation code.
java 8 invoke dynamic advantage
For the first question:
Simply spoken: invokedynamic was very carefully designed (thus it took so long to get it into Java); and one of the side effects of that: is extremely efficient performance wise.
See here for some further reading. Quoting from there:
invokedynamic also benefits dynamic language implementers by supporting dynamically changing call site targets -- a call site, more specifically, a dynamic call site is an invokedynamic instruction. Furthermore, because the JVM internally supports invokedynamic, this instruction can be better optimized by the JIT compiler.
So, long story short: at least theoretically, when you can write code that uses invokedynamic instead of some "old school" way of solving the same problem, new invokedynamic solution has a certain chance of being "faster". As in: it is faster to use invokedynamic compared to the old-school way of creating an anonymous inner class and use that.
Invokedynamic factory creates lambdas as a singleton?
You need to understand what a call-site
is, first, imo; to be able to understand where caching happens. Both exec1
and exec2
will create two separate instances of a Runnable
interface; both will be cached on the call-site
. May be this little snippet will help:
public static void main(String[] args) {
useStatelessLambda1();
useStatelessLambda1();
useStatelessLambda2();
useStatelessLambda2();
}
static void useStatelessLambda1() {
Runnable exec1 = () -> {
System.out.print("Hi from lambda");
};
System.out.print(exec1.hashCode() + " ");
exec1.run();
System.out.println("\n");
}
static void useStatelessLambda2() {
Runnable exec2 = () -> {
System.out.print("Hi from lambda");
};
System.out.print(exec2.hashCode() + " ");
exec2.run();
System.out.println("\n");
}
Running this reveals:
1878246837 Hi from lambda
1878246837 Hi from lambda
1995265320 Hi from lambda
1995265320 Hi from lambda
separate instances, but both cached on the call-site.
Either way, looking at the byte-code will not tell you anything about that. what you could look at is the bootstrap method that invokedynamic
will use : LambdaMetafactory::metafactory
and understand what that will do.
How does invokedynamic instruction decide its description?
The invokedynamic
instruction does not mandate any signature. It’s the code generator which decides for a particular signature and bootstrap method to use, which together define the actual semantic.
E.g., the signature and its meaning is entirely different when using StringConcatFactory.makeConcat(…)
instead of LambdaMetafactory.metafactory(…)
as bootstrap method.
The documentation of these methods and their containing classes describes the behavior thoroughly. But before digging into the bytecode details you should first understand the source code features, i.e. of capturing method references, as explained in What is the equivalent lambda expression for System.out::println. The method reference System.out::println
captures the PrintStream
found in the static field System.out
when instantiating the Consumer
. Hence, the generated factory method consumes a PrintStream
and produces a Consumer
, resulting in the signature (Ljava/io/PrintStream;)Ljava/util/function/Consumer;
.
So, before executing the invokedynamic
instruction, the field has to be read with a GETSTATIC java/lang/System.out
instruction. The DUP; INVOKEVIRTUAL getClass()
sequence is part of an intrinsic null
check which has been discussed in In Java Lambda's why is getClass() called on a captured variable
What's invokedynamic and how do I use it?
It is a new JVM instruction which allows a compiler to generate code which calls methods with a looser specification than was previously possible -- if you know what "duck typing" is, invokedynamic basically allows for duck typing. There's not too much you as a Java programmer can do with it; if you're a tool creator, though, you can use it to build more flexible, more efficient JVM-based languages. Here is a really sweet blog post that gives a lot of detail.
Invokedynamic with non-static context
It’s not correct to say that lambda expressions were always compiled to a static
method. It’s not specified, how they are compiled, which leaves room for two different strategies for a lambda expression that captures this
, like your s -> System.out.println(this)
.
use an instance method:
private void compiler$chosen$name(String s) {
System.out.println(this);
}use a
static
method:private static void compiler$chosen$name(TypeOfThis var0, String s) {
System.out.println(var0);
}
Both methods work equally well when the invokedynamic
instruction points to a bootstrap method in the LambdaMetafactory
. In either case, the invokedynamic
instruction will have a signature consuming a TypeOfThis
instance and producing a Consumer<String>
. From the documentation of the LambdaMetafactory
, you can derive that it will treat the receiver of a non-static
target method like an implied the first argument, which makes the functional signature of both variants identical. All that matters, is that the argument to the consumer’s accept
method has to correspond to the last argument of the list.
I’ve encountered both strategies in practice, so this is indeed compiler dependent.
Note that these strategies also work on source code level, when using method references:
public class Example {
BiConsumer<Example,String> variant1 = Example::instanceMethod;
BiConsumer<Example,String> variant2 = Example::staticMethod;
private void instanceMethod(String s) {
System.out.println(this);
}
private static void staticMethod(Example instance, String s) {
System.out.println(instance);
}
}
This demonstrates the equivalence of a method receiver and the the first argument to a static
method. However, when it comes to binding an argument, only Consumer<String> c = this::instanceMethod;
works with method references. The other binding features of the LambdaMetafactory
are only used by compiler generated code for lambda expressions.
How is stored the result of the invokedynamic?
Each invokedynamic
bytecode refers to a corresponding CONSTANT_InvokeDynamic_info structure in the constant pool. This structure contains a Method Descriptor that is used to derive the types of the arguments and the type of return value for this invokedynamic
instruction.
In your example the method descriptor is ()LI;
computed during source-to-bytecode translation.
8: invokedynamic #4, 0 // InvokeDynamic #0:foo:()LI;
^^^^^
It means that this particular bytecode expects no arguments and always produces the result of type I
.
Lambda Expressions in Java8
In the following line
String s = (String) invoke(() -> true);
It is actually invoke(Callable)
that is getting called. The reason is:
() -> true
is a lambda expression that has zero formal parameter and return a result.- Such a signature (zero parameter, single result) is compatible with the functional method
call()
of theCallable
interface. Note that the interface does not need to have the@FunctionalInterface
annotation, it just needs to have a single abstract method.
If you want to invoke invoke(Runnable)
instead, you will need to create a lambda that is compatible with a functional method that takes zero parameter and returns no result (i.e. conforms with the signature of run()
). Something like this:
invoke(() -> System.out.println("foo"));
Which just prints foo
when ran.
Related Topics
Jsoup Cookies for Https Scraping
How to Simulate a Real Mouse Click Using Java
Accessing Constructor of an Anonymous Class
Access to File Using Java with Samba Jcifs
What in the World Are Spring Beans
Parsing CSV Input with a Regex in Java
Arrays.Fill with Multidimensional Array in Java
Convert Java.Util.Hashmap to Scala.Collection.Immutable.Map in Java
Whats the Best Way to Recursively Reverse a String in Java
Jackson/Hibernate, Meta Get Methods and Serialization
Java 8 Stream with Batch Processing
How to Disable the Default Console Handler, While Using the Java Logging API
How to Serialize Only the Id of a Child with Jackson
How to Split a Text into Sentences Using the Stanford Parser