5

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

Community
  • 1
  • 1
ife
  • 1,231
  • 13
  • 13
  • 1
    You should elaborate the "when I get their name", i.e. tell us how you get the `parameters` array instead of how you print its elements. – Holger Jan 07 '16 at 14:30
  • Which JDK version do you use? This example correctly shows the parameter names on my machine using 1.8.0_60. – Tobias Jan 09 '16 at 18:16

1 Answers1

4

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.

Tobias
  • 7,723
  • 1
  • 27
  • 44
  • I think he is calling `getDeclaredMethods()` on the class of some lambda instance, not on the main class. Like `iface.getClass().getDeclaredMethods()` in your `bar()` method. – Didier L Jan 07 '16 at 12:53
  • 1
    Okay, thanks for the hint, I found the problem and tried to explain it, it is not too simple. – Tobias Jan 07 '16 at 13:46
  • 2
    Well, the dynamically created class apparently exists. However, there are no guaranteed properties, especially, there is no guaranteed 1:1 mapping between these classes and the target methods, hence, you can't expect the generated classes to expose properties of the target methods, like the parameter names. But anyway, parameter names should not carry any information besides the contract and the contract is defined by the *interface*, not by the implementation. – Holger Jan 07 '16 at 14:37
  • 1
    @Holger That's exactly what I tried to explain, thanks! – Tobias Jan 07 '16 at 14:39
  • Thank you so much. I have updated the question, problem still exists. – ife Jan 09 '16 at 17:37