I was wondering how references to methods ( :: :) are implemented in Java 8. For comparison, I will describe how they are implemented in Object Pascal - in the simplest way and efficiently. A pointer to a method in it is a record of two pointers:

 TMethod = record Code, Data: Pointer; end; 

In one, the address of the procedure is stored; in the other, the address of the owner object.

 Button1.OnClick = MyClick1; 

It is broadcast only in copying two pointers.
Calling such a method is a procedure call to an address, with the implicit parameter this being passed.

But I was told here that in Java 8, references to methods are syntactic sugar and that there is something terrible behind them. Is it really true? Has anyone ever watched, in which code on JVM are translated references to methods?

  • 2
    @AntonSorokin and lambdas by themselves are a very complicated thing. This is an invokedynamic instruction that calls the bootstrap method created by the compiler, which in runtime uses the LambdaMetafactory to create CallSite , which returns the MethodHandle that will already be invoked. - Sergey Gornostaev
  • one
    The topic is extremely interesting. Just at a recent JPoint was a report ... Duration two and a half hours. - Sergey Gornostaev
  • Sergey Gornostaev, and you are sure that in the case of references to methods through a lambda expression done? Indeed, in the case of a reference to a function, the object already exists, and the function exists. - user308670 1:56 pm
  • one
    @Eugene firstly, in the books on "FI" on Java, references to methods are usually described in chapters on lambdas. Second, here: System.out::println equivalent to x -> System.out.println(x) , Math::max equivalent to (x,y) -> Math.max(x,y) , String::length equivalent to `x -> x.length ()` - Anton Sorokin
  • one
    I wanted to accept, but did not know how. I thought maybe my reputation still does not allow to do it. With this question, I will wait a little more, and in old ones I will do it .. - user308670

1 answer 1

In Java, references to methods are abbreviated lambda expressions:

In total there are 3 constructions of references to methods:

  1. object::instanceMethod - refers to the object method of the proposed object
  2. Class::staticMethod - refers to a static class method
  3. Class::instanceMethod - refers to the object method of the proposed object. Acts as in step 1, only with the name of the class

Examples of the implementation of these structures and their lambda counterparts below:

  1. System.out::println is x -> System.out.println(x)
  2. Math::max is equal to (x,y) -> Math.max(x,y)
  3. String::length is x -> x.length()

But still, references to methods are implemented a little differently (about this below).

In fact, lambda expressions are similar to anonymous classes with one method, but implemented differently. Detailed analysis of their implementation - below.


A brief sample of the article about how the lambda works under the hood of the JVM :

Anonymous inner classes have undesirable characteristics that may affect the performance of your application. First, the compiler generates a new class file for each anonymous inner class. Creating many class files is undesirable because each class file must be loaded and validated before use, which affects the performance when launching the application. If the lambdas were transferred to anonymous inner classes, you would have a new class file for each lambda. As a result, anonymous inner classes will increase the memory consumption of your application.

Instead of using a separate class for lambda expressions, Java 8 relies on the invokedynamic bytecode instruction added in Java 7. The invokedynamic instruction focuses on the bootstrap method, which, in turn, creates a lambda expression implementation when this method is first called.

Translating lambda expressions into bytecode is performed in two steps:

  1. A dynamic lambda factory is generated, which, when invoked, returns an instance of the functional interface to which the lambda is converted.
  2. The body of the lambda expression is converted to a method that will be invokedynamic .

In the case of references to methods, everything happens almost the same as for lambdas - but javac does not generate a syntactic method (because it already exists in the class from which the method is called, but the lambda does not have such a class), and can refer to the necessary method directly.

To illustrate the first step, let's take a look at the bytecode generated from a simple class containing lambda:

 import java.util.function.Function; public class Lambda { Function<String, Integer> f = s -> Integer.parseInt(s); } 

This class will be generated in the following bytecode:

  0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 10: putfield #3 // Field f:Ljava/util/function/Function; 13: return 

How the second step is performed depends on whether the lambda expression is raw (lambda does not refer to any variables outside its body), or closing (lambda accesses variables outside its body).

Note: closure is when a variable declared outside this expression is used in a lambda expression.

In the first case, lambda expressions are simply transformed into a static method that has the exact same signature of lambda expressions, and are declared in the same class where the lambda expression is used. For example, a lambda expression declared in the Lambda class above can be converted to a method like this:

 static Integer lambda$1(String s) { return Integer.parseInt(s); } 

The case of closure of a lambda expression is slightly more complicated, since the trailing variables must be passed to a method that implements the body of the lambda expression along with the formal arguments of the lambda expression. In this case, the general strategy is to use lambda expression arguments with an additional argument for each external variable. Let's look at a practical example:

 int offset = 100; Function<String, Integer> f = s -> Integer.parseInt(s) + offset; 

The corresponding implementation of the method will be generated as follows:

 static Integer lambda$1(int offset, String s) { return Integer.parseInt(s) + offset; } 

There is also a quote from Brian Getz from this answer to enSO :

When the compiler encounters a lambda expression, it first lowers the body of the lambda into a method (similar to lambda), possibly with some additional arguments (if the lambda is trailing). At the moment when the lambda expression is captured, it generates the dynamic call site, which, when invoked, returns an instance of the functional interface into which the lambda expression was converted. This call is called a lambda factory (lambda factory) for a given lambda. The lambda factory dynamic arguments are values ​​derived from a lexical context. The bootstrap method of the lambda expression factory is a standardized method in the Java language runtime library, called the lambda expression metafab.

References to methods are handled in the same way as lambda expressions, except that most references to methods do not need to be entered into a new method; we can simply load the descriptor for the reference method and pass it to the metafab.


About invokedynamic ( from Habr ):

It is widely known that in the Java virtual machine, starting from version 7, there is an interesting instruction invokedynamic (it is indy ). Many have heard about it, but few know what it does in reality. Someone knows that it is used when compiling lambda expressions and references to methods in Java 8. Some have heard that it is used to concatenate strings in Java 9. But although these are useful uses of indy , the original goal is still slightly different: to do dynamic a call where you can call different code in the same place .


I used two articles as sources - the Java 8 Lambdas A Peek Under the Hood report and an IBM article on lambdas (or rather, the last paragraphs).

In my answer, only a brief squeeze out of the report, so to understand the work of lambda I strongly advise him to read.

There is also a report about invokedynamic from the conference JUG.ru.

  • Did I understand correctly that in the case of a reference to a method, step 1 is not executed? - user308670
  • @Eugene yes, right. - Anton Sorokin