我可以使用Java反射获取方法参数的名字吗?我可以使用Java反射获取方法参数的名字吗?(Can I

2019-05-09 02:36发布

如果我有这样一个类:

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

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

Answer 1:

总结:

  • 如果在编译期间包括调试信息获取的参数名称可能的。 见这个回答更多细节
  • 否则得到的参数名称是不可能
  • 获得参数类型是可能的,使用method.getParameterTypes()

对于编写自动填充功能的编辑器(如你在一个评论说)的缘故,有几个选项:

  • 使用arg0arg1arg2等。
  • 使用intParamstringParamobjectTypeParam等。
  • 使用以上的组合 - 前者用于非原始类型,而后者为原始类型。
  • 不显示参数名在所有 - 只是类型。


Answer 2:

在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文档类的参数


Answer 3:

该Paranamer库的建立是为了解决这个同样的问题。

它试图确定在几个不同的方式方法名。 如果类与调试编译它可以通过读取类的字节码提取信息。

另一种方式是,它是编译后的私人静态成员注入类的字节码,但它被放置在一个罐子里了。 然后,它使用反射来提取的类别在运行时,这个信息。

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

我不得不使用这个库的问题,但我没有得到它到底工作。 我希望报告问题的维护者。



Answer 4:

是。
必须对代码进行编译与Java兼容8编译器选项来存储形式参数名称开启(α参数选项)。
然后,此代码段应工作:

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


Answer 5:

看到org.springframework.core.DefaultParameterNameDiscoverer类

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


Answer 6:

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

但是,你不能告诉使用的参数的名称。



Answer 7:

这是可能和Spring MVC 3做的,但我并没有花时间去看看到底怎么样。

如果您的代码启用调试编译方法参数名URI模板变量名的匹配才能完成。 如果你还没有启用调试,则必须在@PathVariable注释,以变量名的解析值绑定到方法参数指定URI模板变量名的名称。 例如:

摘自春天文档



Answer 8:

虽然这是不可能的(如其他人所示),你可以使用注释来结转参数名,并获得,虽然反映。

不干净的解决方案,但它能够完成任务。 一些web服务真正做到这一点,以保持参数名称(即:部署的WS在GlassFish)。



Answer 9:

见java.beans.ConstructorProperties ,它的设计正是这种做注解。



Answer 10:

所以,你应该能够做到:

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

但是,你可能会得到像这样的列表:

["int : arg0"]

我相信,这将是固定在Groovy 2.5+

所以目前,答案是:

  • 如果它是一个Groovy类,则没有,你不能得到的名字,但你应该能够在未来。
  • 如果它的Java 8下编译Java类,你应该可以。

也可以看看:

  • http://openjdk.java.net/jeps/118
  • https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html

对于每一个方法,则是这样的:

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


Answer 11:

如果你用的是eclipse,看到波纹图像,让编译器来存储方法的参数信息



Answer 12:

参数名称是只对编译器非常有用。 当编译器生成一个类文件,该参数名称不包括 - 一个方法的参数列表中只包括它的参数的数量和种类。 因此,这将是不可能恢复使用反射(如标记你的问题)参数名称 - 它不存在任何地方。

但是,如果使用反射不是硬性要求,你可以检索直接从源代码信息(假设有的话)。



Answer 13:

要添加我的2美分; 参数信息是在一个类文件中提供“用于调试”当你使用javac -g来编译源。 而且它是提供给APT但你需要一个注解所以没有用的你。 (有人讨论类似的4 - 5年前在这里: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

整体而言,在短,除非你在源工作,你不能让它直接文件(类似于在编译时做什么APT)。



Answer 14:

作为@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>

然后,这个类应该做你想要什么。 只需调用静态方法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改变了原来的解决方案,使之成为工作的方法。 如果我正确的理解,他的解决方案仅适用于构造函数。
  • 该解决方案不工作static方法。 这是becasue在这种情况下,由ASM返回参数的数量是不同的,但它的东西,可以很容易地固定。


文章来源: Can I obtain method parameter name using Java reflection?