Saturday, 16 October 2021

Method References

I've been recently reading about method references in Java, particularly interested in whether invokedynamic is also used with them (as it happens with lambdas). The answer is yes and as you can read in that document the main difference is that for lambdas the Java compiler needs to "desugar" the lambda into a private method, which is obviously unnecessary for method references.

When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression, possibly with some additional arguments (for values captured from the lexical scope, if any.) At the point at which the lambda expression would be captured, it generates an invokedynamic call site, which, when invoked, returns an instance of the functional interface to which the lambda is being converted. This call site is called the lambda factory for a given lambda. The dynamic arguments to the lambda factory are the values captured from the lexical scope. The bootstrap method of the lambda factory is a standardized method in the Java language runtime library, called the lambda metafactory. The static bootstrap arguments capture information known about the lambda at compile time (the functional interface to which it will be converted, a method handle for the desugared lambda body, information about whether the SAM type is serializable, etc.)

Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.

This other article gives a deeply interesting explanation of how lambdas and invokedynamic works.

Regarding method references (do not confuse them with the related, low level Method Handles), this article explains them pretty well. As I expected you can get a method reference to an instance method that gets its "this" value ("receiver object" in the article) bound to that instance, and you can get method references to static methods. The not so obvious thing is that you can get references to instance methods that are not bound to a "this" value ("References to unbound non-static methods" in the article). They'll use as "this/receiver object" the first parameter received when being invoked. That's pretty nice, as you can reuse the same method reference object with different receivers.

This has made me think about how delegates work in .Net, I could remember that we have something very similar, about which I wrote in one of my first posts. We call that Open Instance Delegates, but their creation is rather less friendly than in Java, so they remain as a not particularly well known feature. As a reminder:


MethodInfo mInf = typeof(Person).GetMethod("DoSomethingInstance");
  Func openDelegate = (Func)(Delegate.CreateDelegate(typeof(Func), mInf));
  //now let's call our Open Delegate with different instances of Person
  Console.WriteLine(openDelegate(p1,"formatting: ", 3));
  Console.WriteLine(openDelegate(new Person(){Name = "Xurde"},"formatting: ", 4));
  Console.WriteLine(openDelegate(new Person(){Name = "Nel"},"formatting: ", 2));


Another nice feature of Java Method references is that we can have references to constructors, using this syntax: ClassName::new. This is not possible in C#, I think because the CLR does not allow binding delegates to ConstructorInfo objects, only to MethodInfo objects. This is not a big deal, because we can just create a lambda that invokes the constructor: () => MyConstructor(), but anyway, it's nice that Java allows to directly reference the constructor.

In my old post I've seen that C# delegates provide also Close Static Delegates, that is a delegate pointing to a static method and where the delegate Target ("this") is bound to a value that will be passed as the first parameter to the static method. I still can not see much use for this, but well, just another feature.

No comments:

Post a Comment