In this case, the instruction executes the Bootstrap Method #0, which is the “0” argument to the
invokedynamic instruction. That bootstrap methods have their own pool, and in this case, #0 is the static
method metafactory on the class java.lang.invoke.LambdaMetafactory . Every bootstrap method is
automatically passed MethodHandles.Lookup (a utility class to look up methods), the String name of the
invoked method, and the type the CallSite is expected to provide as a MethodType . In this case, the type that
is expected is a Supplier , and we are implementing the get method: that is specified by the other argument
to invokedynamic (the #3).
In the bootstrap method pool, though, we see a few other method arguments declared. These are tacked
onto the end, and they are the three remaining arguments to the metafactory call. The second argument is
the simplest: it is the source of the implementation for the lambda. In this case, it is the static invocation of
our lambdiseMe method. The first and last arguments are the implementation signature and runtime type
of the method being implemented. The implementation signature is something that takes no arguments
and returns an object - ()Ljava/lang/Object; - because that is the signature of Supplier.get() that we
are trying to match. However, we also tell the runtime via the third argument that this method will actually
always return a String - ()Ljava/lang/String; - which is useful for optimization and casting.
Let's pull it all together. The invokedynamic instruction is encountered for the first time. The
metafactory method is called, being passed some default arguments and some custom arguments. The
default arguments are the utility for method lookup, the name of the invoked method, and an instruction
to return a Supplier. The custom arguments specify that we will implement Supplier.get(), and even though
it normally takes no arguments and returns an Object, we will really provide an implementation that takes
no arguments and returns a String. Another custom argument specifies that the implementation of that is
through statically invoking our lambdiseMe method. This bootstrap method returns the details about the call
site, which is invoked to generate the Supplier based on our lambda. Whew!
This is a lot of work on paper, but the shocking part is how fast this is, especially given repeated
invocations. The overhead of lambdas compared to anonymous functions is usually too small to be
measured, especially on any kind of modern hardware. The Java 8 runtime is tuned well for this kind of work,
and it does all this heavy lifting on the first invocation so that later invocations are lightweight and easy to
Given all this work for a static method, though, what happens when we want to call a method on some
bound object, such as our old friend, System.out::println ?
Bound Instance Method References in Bytecode
In the previous section, we saw how a static method was turned into a functional interface implementation
via a method reference. The static case is the simplest; binding an instance method is trickier. An instance
method also has to store its particular instance for later execution, and that instance has to be used to invoke
Throughout this topic, we have used System.out::println as our generic instance method
reference, and so our current example will be the same. Using the bytecode notation, the shape of System.
out::println is (Ljava/lang/String)V - it takes a string and returns void. This is the shape of the
Consumer<String> functional interface class, so our example will produce a Consumer<String> based on
System.out::println . When we do this, we get the result in Listing 8-3.