如何使用反射在Java 8中获取方法参数名称?

2022-09-01 02:28:00

Java 8 能够使用反射 API 获取方法参数名称。

  1. 如何获取这些方法参数名称?

  2. 据我所知,类文件不存储正式的参数名称。如何使用反射获得这些?


答案 1

如何获取这些方法参数名称?

基本上,您需要:

  • 获取对Class
  • 从 中获取对 a 的引用,方法是调用 getDeclaredMethod() 或返回对对象的引用ClassMethodgetDeclaredMethods()Method
  • 从对象中,调用(从 Java 8 开始的新),返回一个对象数组MethodgetParameters()Parameter
  • 在对象上,调用ParametergetName()
Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
   System.err.println(m.getName());
   for (Parameter p : m.getParameters()) {
      System.err.println("  " + p.getName());
   }
}

输出:

...
indexOf
  arg0
indexOf
  arg0
  arg1
...

另外,据我所知.class文件不存储正式参数。那么我如何使用反射来获得它们?

请参阅 javadoc for Parameter.getName()

...如果参数的名称存在,则此方法返回类文件提供的名称否则,此方法将合成 argN 形式的名称,其中 N 是声明参数的方法的描述符中参数的索引。

JDK 是否支持此功能,是特定于实现的(如上面的输出所示,JDK 8 的 build 125 不支持它)。类文件格式支持可选属性,这些属性可以由特定的 JVM/javac 实现使用,而其他不支持它的实现会忽略这些属性。

请注意,您甚至可以使用 , 、 ... 生成上述输出。使用Java 8之前的JVM - 您需要知道的只是参数计数,可以通过以下方式访问:arg0arg1Method.getParameterTypes()

Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
  System.err.println(m.getName());
  int paramCount = m.getParameterTypes().length;
  for (int i = 0;  i < paramCount;  i++) {
    System.err.println("  arg" + i);
  }
}

JDK 8 的新功能是有一个扩展的 API,并且 JVM 可以提供真实的参数名称,而不是 、 、 ...arg0arg1

通过可附加到各种类文件结构的可选属性,可以支持此类可选功能。请参阅 4.6。类文件中结构的方法。另请参见 4.7.1。在 JVM 规范中定义和命名新属性method_info

由于使用 JDK 8,类文件版本将递增到 52,因此也可以更改文件格式本身以支持此功能。

另请参阅 JEP 118:在运行时访问参数名称,以获取更多信息和实现备选方案。建议的实现模型是添加一个存储参数名称的可选属性。由于类文件格式已经支持这些可选属性,这甚至可以以某种方式实现,以便较旧的JVM仍然可以使用类文件,其中它们被简单地忽略,如规范要求的那样:

Java 虚拟机实现需要以静默方式忽略它们无法识别的属性。

更新

正如@assylias所建议的那样,需要使用命令行选项编译源代码,以便将参数名称反射的元数据添加到类文件中。但是,这当然只会影响使用此选项编译的代码 - 上面的代码仍将打印,等等,因为运行时库不是使用此标志编译的,因此在类文件中不包含必要的条目。javac-parametersarg0arg1


答案 2

谢谢安德烈亚斯,但最后我从oracle方法参数教程中得到了完整的解决方案

它说,

您可以使用方法java.lang.reflect.Executable.getParameters获取任何方法或构造函数的形式参数的名称。(类 Method 和构造函数扩展了类 Executable,因此继承了方法 Executable.getParameters。但是,默认情况下,.class文件不存储正式的参数名称。这是因为许多生成和使用类文件的工具可能不希望包含参数名称的.class文件占用更大的静态和动态占用空间。特别是,这些工具必须处理更大的.class文件,而Java虚拟机(JVM)将使用更多的内存。此外,某些参数名称(如 secret 或 password)可能会公开有关安全敏感方法的信息。

要将正式参数名称存储在特定的.class文件中,从而使反射 API 能够检索正式参数名称,请使用 -parameters 选项将源文件编译到 javac 编译器

如何编译

Remember to compile the with the -parameters compiler option

预期输出(有关完整示例,请访问上面提到的链接)

java MethodParameterSpy ExampleMethods

此命令打印以下内容:

Number of constructors: 1

Constructor #1
public ExampleMethods()

Number of declared constructors: 1

Declared constructor #1
public ExampleMethods()

Number of methods: 4

Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
             Return type: boolean
     Generic return type: boolean
         Parameter class: class java.lang.String
          Parameter name: stringParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: int
          Parameter name: intParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
             Return type: int
     Generic return type: int
         Parameter class: class [Ljava.lang.String;
          Parameter name: manyStrings
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)
             Return type: boolean
     Generic return type: boolean
         Parameter class: interface java.util.List
          Parameter name: listParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)
             Return type: void
     Generic return type: void
         Parameter class: class [Ljava.lang.Object;
          Parameter name: a
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: interface java.util.Collection
          Parameter name: c
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false