javalambdareflectionjava-14

How to get the MethodInfo of a Java 14 method reference?


I'm essentially asking the same as this old question, but for Java 14 instead of Java 8. To spare answerers the trouble of navigating to the old question, I'll rephrase it here. I want to get the name of a function from a referenced method. The following Java code should give you the idea:

public class Main
{
    public static void main(String[] args)
    {
       printMethodName(Main::main);
    }

    private static void printMethodName(Consumer<String[]> theFunc)
    {
        String funcName = // somehow get name from theFunc
        System.out.println(funcName)
    }
 }

The equivalent in C# would be:

public class Main
{
    public static void Main()
    {
        var method = Main.Main;
        PrintMethodName(method)
    }

    private static void PrintMethodName(Action action)
    {
        Console.WriteLine(action.GetMethodInfo().Name);
    }
}

According to the accepted answer of the old question, this was not possible in Java 8 without considerable work, such as this solution. Is there a more elegant solution in Java 14?


Solution

  • Getting a method info from a method reference never was a goal on the JDK developer’s side, so no effort was made to change the situation.

    However, the approach shown in your link can be simplified. Instead of serializing the information, patching the serialized data, and restoring the information using a replacement object, you can simply intercept the original SerializedLambda object while serializing.

    E.g.

    public class GetSerializedLambda extends ObjectOutputStream {
        public static void main(String[] args) { // example case
            var lambda = (Consumer<String[]>&Serializable)GetSerializedLambda::main;
            SerializedLambda sl = GetSerializedLambda.get(lambda);
            System.out.println(sl.getImplClass() + " " + sl.getImplMethodName());
        }
    
        private SerializedLambda info;
    
        GetSerializedLambda() throws IOException {
          super(OutputStream.nullOutputStream());
          super.enableReplaceObject(true);
        }
    
        @Override protected Object replaceObject(Object obj) throws IOException {
          if(obj instanceof SerializedLambda) {
            info = (SerializedLambda)obj;
            obj = null;
          }
          return obj;
        }
    
        public static SerializedLambda get(Object obj) {
            try {
                GetSerializedLambda getter = new GetSerializedLambda();
                getter.writeObject(obj);
                return getter.info;
            } catch(IOException ex) {
                throw new IllegalArgumentException("not a serializable lambda", ex);
            }
        }
    }
    

    which will print GetSerializedLambda main. The only newer feature used here, is the OutputStream.nullOutputStream() to drop the written information immediately. Prior to JDK 11, you could write into a ByteArrayOutputStream and drop the information after the operation which is only slightly less efficient. The example also using var, but this is irrelevant to the actual operation of getting the method information.

    The limitations are the same as in JDK 8. It requires a serializable method reference. Further, there is no guaranty that the implementation will map to a method directly. E.g., if you change the example’s declaration to public static void main(String... args), it will print something like lambda$1 when being compiled with Eclipse. When also changing the next line to var lambda = (Consumer<String>&Serializable)GetSerializedLambda::main;, the code will always print a synthetic method name, as using a helper method is unavoidable. But in case of javac, the name is rather something like lambda$main$f23f6912$1 instead of Eclipse’s lambda$1.

    In other words, you can expect encountering surprising implementation details. Do not write applications relying on the availability of such information.