【问题标题】:How to get Method Parameter names in java 8 lambda expression?如何在 java 8 lambda 表达式中获取方法参数名称?
【发布时间】:2016-01-07 08:59:58
【问题描述】:

来自How to get Method Parameter names in Java 8 using reflection? 我知道使用 javac -parameters 参数可以将参数名称保留在 *.class 文件中。 但在 lambda 表达式中无效?

示例:

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);
    }

}

当我这样做时

javac -parameters MyTest.java
java MyTest

打印出来

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

我尝试做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
}

我可以找到参数名称iface,但找不到x或y

【问题讨论】:

  • 您应该详细说明“当我得到他们的名字时”,即告诉我们您如何获得parameters 数组,而不是如何打印其元素。
  • 您使用哪个 JDK 版本?此示例正确显示了我使用 1.8.0_60 的机器上的参数名称。

标签: java lambda java-8


【解决方案1】:

这似乎不是 lambda 表达式本身的问题:

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));
  }

}

使用单个 lambda 表达式来保持简单。使用-parameters 选项编译后,我们可以使用javap -c -p -verbose Test 了解更多信息:

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

参数名称(x 和y)在那里!迭代这样的方法

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

正确显示参数名称:

lambda$foo$0
 => x
 => y

不是 lamdbas 的问题,实际上很难确定正确的方法。如果您尝试在传递给该方法的接口实例上使用getDeclaredMethods() 获取参数名称(正如@Didier L 在评论中建议的那样),您将遇到问题。例如,使用

iface.getClass().getDeclaredMethods()

bar() 不会像您预期的那样工作。如果您检索类的名称,例如比如iface.getClass().getName(),你会看到这样的:

Test$$Lambda$1/834600351

这是一个动态创建的类,你可以争论它是否存在。由于它不是由编译器生成的,因此它不会公开任何有关局部变量或其名称的信息,因为接口实现的方法只是与您的 lambda 不同。这是一个重要的区别。

这个“虚拟类”提供了一个方法,例如doSomething(int, int),用于实现给定的接口 (MyInterface),但这个公开的方法与您创建的定义 lambda 的方法 (lambda$foo$0) 不同。

因此,生成的“虚拟类”的方法doSomething没有携带参数信息。动态创建的类“隐藏”了您的实现。


你的第二个例子没有这个问题:

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

您明确定义了一个实现接口A 的类,并且您明确提供了一个方法exec,因此所有信息都存在。

【讨论】:

  • 我认为他是在某个 lambda 实例的类上调用getDeclaredMethods(),而不是在主类上。就像您的 bar() 方法中的 iface.getClass().getDeclaredMethods() 一样。
  • 好的,谢谢提示,我发现了问题,试着解释一下,不太简单。
  • 好吧,动态创建的类显然是存在的。但是,没有保证的属性,尤其是这些类和目标方法之间没有保证的 1:1 映射,因此,您不能期望生成的类公开目标方法的属性,例如参数名称。但无论如何,参数名称不应该携带除合约之外的任何信息,并且合约是由接口定义的,而不是由实现定义的。
  • @Holger 这正是我试图解释的,谢谢!
  • 非常感谢。我已经更新了问题,问题仍然存在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多