我可以使用Java反射获取方法参数名称吗?


124

如果我有这样的课程:

public class Whatever
{
  public void aMethod(int aParam);
}

有没有办法知道aMethod使用名为aParam类型的参数int


10
7个答案,没有人提到“方法签名”一词。基本上,您可以通过反射进行内省,这意味着:没有参数名称。
ewernli

3
可能的,看到我的回答如下
flybywire

4
6年后,现在可以通过Java 8中的反射实现了,请参见此答案
earcam

Answers:


90

总结一下:

  • 如果编译期间包含调试信息,可以获取参数名称。查看此答案以获取更多详细信息
  • 否则得到的参数名称是不是有可能
  • 使用可以获取参数类型 method.getParameterTypes()

为了编写编辑器的自动完成功能(如您在评论之一中所述),有几个选项:

  • 使用arg0arg1arg2等。
  • 使用intParamstringParamobjectTypeParam,等。
  • 结合使用以上两种方法-前者用于非基本类型,后者用于基本类型。
  • 根本不显示参数名称-仅显示类型。

4
接口有可能吗?我仍然找不到获取接口参数名称的方法。
Cemo

@Cemo:您是否能够找到获取接口参数名称的方法?
Vaibhav Gupta

只需添加一下,这就是为什么spring需要@ConstructorProperties批注以从原始类型明确创建对象的原因。
巴拉特

102

在Java 8中,您可以执行以下操作:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

public final class Methods {

    public static List<String> getParameterNames(Method method) {
        Parameter[] parameters = method.getParameters();
        List<String> parameterNames = new ArrayList<>();

        for (Parameter parameter : parameters) {
            if(!parameter.isNamePresent()) {
                throw new IllegalArgumentException("Parameter names are not present!");
            }

            String parameterName = parameter.getName();
            parameterNames.add(parameterName);
        }

        return parameterNames;
    }

    private Methods(){}
}

因此,对于您的课程,Whatever我们可以进行手动测试:

import java.lang.reflect.Method;

public class ManualTest {
    public static void main(String[] args) {
        Method[] declaredMethods = Whatever.class.getDeclaredMethods();

        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.getName().equals("aMethod")) {
                System.out.println(Methods.getParameterNames(declaredMethod));
                break;
            }
        }
    }
}

[aParam]如果您已将-parameters参数传递给Java 8编译器,则应打印该文本。

对于Maven用户:

<properties>
    <!-- PLUGIN VERSIONS -->
    <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>

    <!-- OTHER PROPERTIES -->
    <java.version>1.8</java.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <!-- Original answer -->
                <compilerArgument>-parameters</compilerArgument>
                <!-- Or, if you use the plugin version >= 3.6.2 -->
                <parameters>true</parameters>
                <testCompilerArgument>-parameters</testCompilerArgument>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>

有关更多信息,请参见以下链接:

  1. 官方Java教程:获取方法参数的名称
  2. JEP 118:在运行时访问参数名称
  3. 参数类的Javadoc

2
我不知道他们是否更改了编译器插件的参数,但是对于最新版本(截至目前为3.5.1),我不得不在配置部分中使用编译器args:<configuration> <compilerArgs> <arg>-参数</ arg> </ compilerArgs> </ configuration>
最长

15

创建Paranamer库就是为了解决这个问题。

它试图以几种不同的方式确定方法名称。如果该类是通过调试编译的,则可以通过读取该类的字节码来提取信息。

另一种方法是,在编译后但将其放入罐子之前,将私有静态成员注入到该类的字节码中。然后,它使用反射在运行时从类中提取此信息。

https://github.com/paul-hammant/paranamer

我在使用该库时遇到了问题,但最终还是可以使它正常工作。我希望将这些问题报告给维护人员。


我正在尝试在android apk中使用paranamer。但我得到ParameterNAmesNotFoundException
Rilwan

10

参见org.springframework.core.DefaultParameterNameDiscoverer类

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));

10

是。必须使用兼容Java 8的编译器来编译
代码,带有用于存储形式参数名称的选项(-parameters选项)。
然后,此代码段应该工作:

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

尝试了一下,它起作用了!不过,有一个提示,我必须重建您的项目才能使这些编译器配置生效。
ishmaelMakitla

5

您可以使用反射检索方法并检测其参数类型。检查http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html#getParameterTypes%28%29

但是,您不能说出所使用参数的名称。


17
的确是这样。但是,他的问题明确涉及参数名称。检查主题标题。
BalusC 2010年

1
我引用:“但是,您不能说出所使用参数的名称。” 刚读完我的答案-_-
Johnco

3
问题并不是以他不知道如何获得类型的方式提出的。
BalusC 2010年

3

Spring MVC 3可以做到这一点,但是我没有花时间去看确切的方法。

方法参数名称与URI模板变量名称的匹配只能在您的代码启用调试的情况下进行编译。如果未启用调试,则必须在@PathVariable批注中指定URI模板变量名称的名称,以便将变量名称的解析值绑定到方法参数。例如:

摘自Spring文档


org.springframework.core.Conventions.getVariableName(),但我想您必须将cglib作为依赖项
Radu Toader 2015年

3

尽管这是不可能的(正如其他人所说明的那样),您可以使用注释来继承参数名称,并通过反射获得该名称。

这不是最干净的解决方案,但可以完成工作。实际上,某些Web服务会这样做以保留参数名称(即:使用glassfish部署WS)。



3

因此,您应该能够:

Whatever.declaredMethods
        .find { it.name == 'aMethod' }
        .parameters
        .collect { "$it.type : $it.name" }

但是您可能会得到如下列表:

["int : arg0"]

我相信这将在Groovy 2.5+中修复

因此,目前的答案是:

  • 如果是Groovy类,则不能,您不能获得名称,但将来应该可以。
  • 如果它是在Java 8下编译的Java类,则应该可以。

也可以看看:


对于每种方法,则类似于:

Whatever.declaredMethods
        .findAll { !it.synthetic }
        .collect { method -> 
            println method
            method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
        }
        .each {
            println it
        }

我也不想指定方法的名称aMethod。我想为一个类中的所有方法获取它。
窥探

@snoop添加了获取每种非合成方法的示例
tim_yates

我们不能antlr用来获取参数名称吗?
窥探

2

如果使用eclipse,请参见下面的图片,以允许编译器存储有关方法参数的信息

在此处输入图片说明


2

如@Bozho所述,如果在编译过程中包含调试信息,则可以执行此操作。这里有个很好的答案...

如何获取对象的构造函数的参数名称(反射)?通过@AdamPaynter

...使用ASM库。我整理了一个示例,展示了如何实现目标。

首先,从具有这些依赖项的pom.xml开始。

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-all</artifactId>
    <version>5.2</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

然后,该课程应做您想要的。只需调用static方法getParameterNames()

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;

public class ArgumentReflection {
    /**
     * Returns a list containing one parameter name for each argument accepted
     * by the given constructor. If the class was compiled with debugging
     * symbols, the parameter names will match those provided in the Java source
     * code. Otherwise, a generic "arg" parameter name is generated ("arg0" for
     * the first argument, "arg1" for the second...).
     * 
     * This method relies on the constructor's class loader to locate the
     * bytecode resource that defined its class.
     * 
     * @param theMethod
     * @return
     * @throws IOException
     */
    public static List<String> getParameterNames(Method theMethod) throws IOException {
        Class<?> declaringClass = theMethod.getDeclaringClass();
        ClassLoader declaringClassLoader = declaringClass.getClassLoader();

        Type declaringType = Type.getType(declaringClass);
        String constructorDescriptor = Type.getMethodDescriptor(theMethod);
        String url = declaringType.getInternalName() + ".class";

        InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url);
        if (classFileInputStream == null) {
            throw new IllegalArgumentException(
                    "The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: "
                            + url + ")");
        }

        ClassNode classNode;
        try {
            classNode = new ClassNode();
            ClassReader classReader = new ClassReader(classFileInputStream);
            classReader.accept(classNode, 0);
        } finally {
            classFileInputStream.close();
        }

        @SuppressWarnings("unchecked")
        List<MethodNode> methods = classNode.methods;
        for (MethodNode method : methods) {
            if (method.name.equals(theMethod.getName()) && method.desc.equals(constructorDescriptor)) {
                Type[] argumentTypes = Type.getArgumentTypes(method.desc);
                List<String> parameterNames = new ArrayList<String>(argumentTypes.length);

                @SuppressWarnings("unchecked")
                List<LocalVariableNode> localVariables = method.localVariables;
                for (int i = 1; i <= argumentTypes.length; i++) {
                    // The first local variable actually represents the "this"
                    // object if the method is not static!
                    parameterNames.add(localVariables.get(i).name);
                }

                return parameterNames;
            }
        }

        return null;
    }
}

这是一个单元测试的例子。

public class ArgumentReflectionTest {

    @Test
    public void shouldExtractTheNamesOfTheParameters3() throws NoSuchMethodException, SecurityException, IOException {

        List<String> parameterNames = ArgumentReflection
                .getParameterNames(Clazz.class.getMethod("callMe", String.class, String.class));
        assertEquals("firstName", parameterNames.get(0));
        assertEquals("lastName", parameterNames.get(1));
        assertEquals(2, parameterNames.size());

    }

    public static final class Clazz {

        public void callMe(String firstName, String lastName) {
        }

    }
}

您可以在GitHub上找到完整的示例

注意事项

  • 我稍微更改了@AdamPaynter的原始解决方案,使其适用于Methods。如果我正确理解,他的解决方案仅适用于构造函数。
  • 该解决方案不适用于static方法。这是因为在这种情况下,ASM返回的参数数量有所不同,但这很容易解决。

0

参数名称仅对编译器有用。编译器生成类文件时,不包括参数名称-方法的参数列表仅由其参数的数量和类型组成。因此,不可能使用反射(如您的问题中标记的那样)检索参数名称-它在任何地方都不存在。

但是,如果不是很需要使用反射,则可以直接从源代码中检索此信息(假设您已掌握)。


2
参数名称不仅对编译器有用,而且还可以将信息传达给库的使用者,假设我编写了一个类StrangeDate,如果您要使用它,则该方法具有add(int day,int hour,int minutes)方法, add(int arg0,int arg1,int arg2)那会有用吗?
大卫·沃特斯

抱歉-您完全误解了我的回答。请在问题的上下文中重新阅读。
danben 2010年

2
获取参数名称对于以反射方式调用该方法有很大的好处。它不仅对编译器有用,即使在问题的上下文中也是如此。
corsiKa 2014年

Parameter names are only useful to the compiler.错误。查看翻新库。它使用动态接口来创建REST API请求。它的功能之一是能够在URL路径中定义占位符名称,并将这些占位符替换为其相应的参数名称。
TheRealChx101 '19

0

再加上我的2美分;当您使用javac -g编译源代码时,参数信息在类文件“用于调试”中可用。它对APT可用,但是您需要一个注释,因此对您没有用。(有人在4-5年前在这里讨论过类似的内容:http : //forums.java.net/jive/thread.jspa?messageID=13467&tstart=0

总的来说,除非直接处理源文件(类似于APT在编译时所做的工作),否则您将无法获得它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.