Java 8 introduces support for first-class functions, which allows assigning functions to variables. In this case the variables must be of a function type, which is defined by a functional interface (an interface with just one abstract method).
So, considering an example of an interface I
and a class A
with the following definition:
interface I{ int foo(); }
class A implements I{
public int foo(){return 7;}
public static int bar(){return 11;}
}
We can assign to a variable of type I
an instance of A
or a method reference to the method bar
of A
. Both can be store on variables of type I
, such as:
I i1 = new A();
I i2 = A::bar;
If we analyze the bytecodes resulting from the compilation of the previous code we will get:
0: new #2 // class A
3: dup
4: invokespecial #3 // Method A."<init>":()V
7: astore_1
8: invokedynamic #4, 0 // InvokeDynamic #0:foo:()LI;
13: astore_2
For i1 = new A();
that is clearly that the corresponding instruction 7: astore_1
is storing an instance of A
that is compatible with I
. But, as a result of the i2 = A::bar
we are storing the result of the 8: invokedynamic #4, 0
.
So, that means that the result of an invokedynamic
is always an instance of the target type, which is the type of the variable that we are assigning with a method reference?
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
.