Conversion of method reference to MethodHandle

Remi Forax forax at univ-mlv.fr
Sun Dec 30 16:42:09 PST 2012


[to lambda spec list and JSR292 EG list]

I want to close one problem that we (the JSR 292 EG) have postponed to 
Java 8 when we were developing the JSR 292,
a Java syntax to reference a method or a constructor as a method handle.
This item was postponed for two reasons, the first one is that the JSR 
292 was a JSR allowing to change the VM spec and not the Java spec and 
the second one was that at that time the syntax for method reference was 
in limbo and we didn't want to choose a syntax that may be incompatible 
with the upcoming lambda spec.

Now that the syntax part of the lambda spec is frozen, we can solve that 
issue.

Just before starting, I want to briefly explain why this syntax is needed.
A runtime of a dynamic languages uses several well known function 
(usually specified as static method) implementing either part of the 
runtime logic like a function that check is the runtime class of an 
object is equals to a specified class or some global functions of the 
language it self (here is the file for JavaScript in Nashorn [1]).
The main issue is that to be used in JSR 292 ecosystem, this functions 
must be handled by method handles and these method handles are defined 
are static final fields,
because Java doesn't initialize these fields lazily, this greatly 
impacts the startup time of any dynamic runtime that runs on the JVM.

So the goals of the syntax that convert a method reference to a method 
handle is:
   - to move some runtime checks at compile time because creating a 
method handle involve using a string and a signature.
   - to lazily create these method handle when needed (like the lambda).

The proposed syntax is to use the same syntax as the method reference, 
so no new syntax.
   MethodHandle mh = String::length;
the target type is a j.l.i.MethodHandle instead of being a functional 
interface

For the semantics:
   Unlike the method reference with a functional interface, only 
unbunded method reference are allowed, so by example,
   a method reference starting with an expression or super are not valid.
   Moreover, because there is no type information that the compiler can 
extract from a MethodHandle, the semantics in case of several 
overloading methods, i.e. several applicable methods is simple, it's an 
ambiguity and is rejected by the compiler.
   More formally, a method reference can be converted to a 
j.l.i.MethodHandle,
     - only if it's form is
        Type::name, with Type an existing type and name a valid Java 
identifier or 'new'.
     - then from the type, the compiler gather all accessible methods 
declared in the type type or it's super type (class or interfaces),
       if a method is override-equivalent to another one, the one from 
the supertype is discarded.
       if the name is 'new', the compiler gather all constructors of the 
type type.
       if the type is an array, all public methods of j.l;Object are 
available plus clone()
     - if there are more than one method in the set of gathered method, 
the reference is ambiguous.
     - if there is no method, the reference is not available
     - if there is one method, a method handle will be created from this 
method.

Here is some examples:
class Test {
   public static void foo() { }
}
...
MethodHandle mh1 = Test::foo;   // foo()V
mh1.invokeExact();

class Test
   public void bar() { }
}
...
MethodHandle mh2 = Test::bar;   // bar(Test)V
mh2.invokeExact(new Test());

class Test {
   interface I {
     public void m();
   }
   static class A implements I {
     public void m() { }
   }
}
...
MethodHandle mh3 = I::m;   // m(I)V
mh3.invokeExact((I)new A());

class Test {
   static class B {
      public B(int value) { }
      }
   }
}
...
MethodHandle mh4 = B::new;   // B(int)
B b = (B)mh4.invokeExact(3);

class Test {
   class C {  // inner class
     public C() { }
   }
}
...
MethodHandle mh5 = C::new;     // C(Test)
C c = (C)mh5.invokeExact(new Test());

class Test {
   static class D {
     Object foo() { }
   }
   static class E {
     String foo() { }  // covariant return type
   }
}
...
MethodHandle mh8 = E::foo;           // foo(E)String
String s3 = (String)mh8.invokeExact(new E());

class Test {
   static class F {
     private static void m() {  } // non visible method for the VM, ok 
in Java, so the compiler has to generate a bridge
   }
}
...
MethodHandle mh9 = F::m;
mh9.invoke();

MethodHandle mh10 = String[]::clone;    // String[].clone returns 
Object, so it needs a bridge by the compiler
String[] values = (String[])mh10.invoke(args);

As you can see the syntax already exist, the semantics of the 
attribution (find the correct method) is simpler than the method call 
semantics (there is no inference) and after the attribution, the rules 
for the generation, bridge, etc. are the same as the one for the method 
reference with a functional interface as target.

The compiler generates like the method reference an invokedynamic with a 
different bootstrap method that takes the constant method handle as 
parameter and create a constant callsite with this constant method 
handle (so at runtime a method reference seen as a method handle is 
always constant).

And because Brian will say that this is interesting but Oracle has no 
resource that can work on this,
here is the patch for the compiler* part based on lambda/langtools : 
http://www-igm.univ-mlv.fr/~forax/tmp/method-ref-method-handle-langtools.diff
and the patch for the jdk part based on lambda/jdk: 
http://www-igm.univ-mlv.fr/~forax/tmp/method-ref-method-handle-jdk.diff

cheers,
Rémi
* note, the mh9 doesn't work at runtime because it also doesn't work for 
method reference with a functional interface.

[1] 
http://hg.openjdk.java.net/nashorn/jdk8/nashorn/file/b4b05457b8b2/src/jdk/nashorn/internal/runtime/GlobalFunctions.java



More information about the lambda-spec-experts mailing list