Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get Method Parameter names in java 8 lambda expression?

from How to get Method Parameter names in Java 8 using reflection? I know using javac -parameters argument can keep the parameter names in *.class file. but it is invalid in lambda expression?

example:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MyTest {
    public static void main(String[] args) {
        for(Method m : Test.class.getDeclaredMethods()) {
            System.out.println(m.getName());
            for(Parameter p : m.getParameters()) {
                System.out.println(" => " + p.getName());
            }
        }
    }
}
interface MyInterface {
    Object doSomething(int a, int b);
}

class Test {

    private void bar(MyInterface iface) {
    }

    public void foo() {
        bar((x, y) -> null);
    }

}

When I do

javac -parameters MyTest.java
java MyTest

It print

bar
 => iface
foo
lambda$foo$0
 => arg0
 => arg1

I try to do javap -c -p -verbose Test :

{
  Test();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 21: 0

  private void bar(MyInterface);
    descriptor: (LMyInterface;)V
    flags: ACC_PRIVATE
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 24: 0
    MethodParameters:
      Name                           Flags
      iface

  public void foo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #2,  0              // InvokeDynamic #0:doSomething:()LMyInterface;
         6: invokespecial #3                  // Method bar:(LMyInterface;)V
         9: return
      LineNumberTable:
        line 27: 0
        line 28: 9

  private static java.lang.Object lambda$foo$0(int, int);
    descriptor: (II)Ljava/lang/Object;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=2, args_size=2
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 27: 0
}

I can find parameter name iface , but can not find x or y

like image 941
ife Avatar asked Sep 26 '22 17:09

ife


1 Answers

This does not appear to be a problem with lambda expressions themselves:

interface MyInterface {
  void doSomething(int a, int b);
}

class Test {

  private void bar(MyInterface iface) {
    iface.doSomething(0, 0);
  }

  public void foo() {
    bar((x, y) -> System.out.println(x));
  }

}

Uses a single lambda expression to keep it simple. After compiling this with the -parameters option, we can use javap -c -p -verbose Test to find out more:

private static void lambda$foo$0(int, int);
  descriptor: (II)V
  flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
  Code:
    stack=2, locals=2, args_size=2
       0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: iload_0
       4: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
       7: return
    LineNumberTable:
      line 15: 0
  MethodParameters:
    Name                           Flags
    x                              synthetic
    y                              synthetic

The parameter names (x and y) are there! Iterating over the methods like this

for(Method m : Test.class.getDeclaredMethods()) {
  System.out.println(m.getName());
  for(Parameter p : m.getParameters()) {
    System.out.println(" => " + p.getName());
  }
}

displays the parameter names correctly:

lambda$foo$0
 => x
 => y

Instead of being a problem with the lamdbas, it is actually just difficult to determine the correct method. If you try to get the parameter names using getDeclaredMethods() on the instance of the interface passed to that method (as @Didier L suggested in a comment), you will run into problems. For example, using

iface.getClass().getDeclaredMethods()

in bar() will not work as you might expect. If you retrieve the name of the class, e.g. like iface.getClass().getName(), you will see something like this:

Test$$Lambda$1/834600351

This is a dynamically created class, you could argue about whether it exists at all. As it is not generated by the compiler, it does not expose any information about local variables or their names because the method implemented by the interface simply is not the same as your lambda. This is an important difference.

This "virtual class" provides a method, e.g. doSomething(int, int), to implement the given interface (MyInterface), but this exposed method is not the same as the method you create be defining a lambda (lambda$foo$0).

Therefore, the method doSomething of the generated "virtual class" does not carry the parameter information. The dynamically created class "hides" your implementation.


Your second example does not suffer from this problem:

map("bibi", new A() {
    @Override
    public JSONObject exec(String u, String s, String b) {
        return null;
    }
});

You explicitely define a class implementing the interface A and you explicitely provide a method exec, therefore all the information is present.

like image 50
Tobias Avatar answered Sep 29 '22 06:09

Tobias