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

42

Java 8具有使用反射API获取方法参数名称的功能。

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

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

4个回答

50

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

基本上,你需要:

  • 获得一个 Class 的引用
  • Class 中通过调用 getDeclaredMethod()getDeclaredMethods() 获取对 Method 的引用,它们返回对 Method 对象的引用
  • Method 对象中调用(自 Java 8 新增)getParameters(),该方法返回一个 Parameter 对象数组
  • Parameter 对象上调用 getName()
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文件不存储正式参数。那么如何使用反射获取它们呢?请参阅Parameter.getName()的Javadoc:
“……如果参数的名称存在,则此方法返回类文件提供的名称。否则,此方法合成一个名称,格式为argN,其中N是声明参数的方法描述符中的参数索引。”
JDK是否支持这一点取决于具体的实现(如您可以从JDK 8版本125的上述输出中看到,该版本不支持它)。类文件格式支持可选属性,这些属性可以由特定的JVM / javac实现使用,并且被不支持它的其他实现忽略。
请注意,即使在Java 8之前的JVM中,您甚至也可以使用Method.getParameterTypes()访问的参数计数来生成上述输出:arg0arg1等。
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可以提供实际参数名称而不是arg0、arg1等。支持此类可选功能是通过可选属性来实现的,这些属性可以附加到各种类文件结构上。请参见4.6. Methods中的class文件中的method_info结构。在JVM规范中还可以查看4.7.1. Defining and Naming New Attributes。由于JDK 8的类文件版本将增加到52,因此更改文件格式以支持此功能也是可能的。
请参阅JEP 118:运行时访问参数名称以获取更多信息和实现替代方案。建议的实现模型是添加一个可选属性来存储参数名称。由于类文件格式已经支持这些可选属性,因此甚至可以以一种方式进行操作,使得旧的JVM仍然可以使用类文件,其中它们被规范要求忽略:

Java虚拟机实现需要默默地忽略它们不认识的属性。

更新

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


但是,我仍然不清楚类文件如何存储方法参数名称。 - Prateek
我在询问Java8,而你提到了JVM/SE7的链接。 - Prateek
是的 - 就我所知,目前还没有官方的Java 8规范。当然,类文件格式不会有重大改变,因此Java 7规范仍然有效 - 至少Java 8类文件仍将支持可选属性,并且这就是链接章节中描述的内容...另请参阅我的更新答案以获取更多信息。 - Andreas Fester
8
你需要使用“-parameter”标志进行编译。 - assylias
4
这个标记的名称是“-parameters”(末尾有's')。 - Lukasz Wiktor
显示剩余7条评论

15

感谢Andreas,但是我最终从Oracle教程中获得了完整的解决方案,链接为Method Parameters

它说:

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

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

如何编译

请记住使用-parameters编译器选项进行编译

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

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

7
您可以使用Paranamer库(https://github.com/paul-hammant/paranamer)。
以下是我测试通过的示例代码:
import com.thoughtworks.paranamer.AnnotationParanamer;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;

Paranamer info = new CachingParanamer(new AnnotationParanamer(new BytecodeReadingParanamer()));

Method method = Foo.class.getMethod(...);
String[] parameterNames = info.lookupParameterNames(method);

如果您使用的是Maven,则请将以下依赖项放入pom.xml文件中:
<dependency>
    <groupId>com.thoughtworks.paranamer</groupId>
    <artifactId>paranamer</artifactId>
    <version>2.8</version>
</dependency>

7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接