ChatGPT解决这个技术问题 Extra ChatGPT

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

如果我有这样的课程:

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

有什么方法可以知道 aMethod 使用了一个名为 aParam 的参数,它的类型是 int

7个答案,没有人提到“方法签名”这个词。这基本上是您可以通过反射自省的内容,这意味着:没有参数名称。
有可能,请参阅下面的答案
年后,现在可以通过 Java 8 中的反射实现,请参阅this SO answer

b
bernie

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

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

对于 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>

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

官方 Java 教程:获取方法参数名称 JEP 118:在运行时访问参数名称 Javadoc for Parameter class


我不知道他们是否更改了编译器插件的参数,但是对于最新版本(截至目前为 3.5.1),我必须在配置部分使用编译器参数: -参数
要为 Gradle 传递参数,请按照以下说明操作:stackoverflow.com/a/37482148/3854385
C
Community

总结一下:

如果在编译期间包含调试信息,则可以获取参数名称。有关更多详细信息,请参阅此答案

否则无法获取参数名称

可以使用 method.getParameterTypes() 获取参数类型

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

使用 arg0、arg1、arg2 等。

使用 intParam、stringParam、objectTypeParam 等。

使用上述组合 - 前者用于非原始类型,后者用于原始类型。

根本不显示参数名称 - 只显示类型。


这可以通过接口实现吗?我仍然找不到获取接口参数名称的方法。
@Cemo:你能找到一种方法来获取接口参数名称吗?
补充一下,这就是为什么 spring 需要 @ConstructorProperties 注释来明确地从原始类型创建对象。
S
Sarel Botha

创建 Paranamer 库是为了解决同样的问题。

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

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

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

我在使用这个库时遇到了问题,但最终我确实让它工作了。我希望将问题报告给维护者。


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

见 org.springframework.core.DefaultParameterNameDiscoverer 类

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

K
Karol Król

是的。代码必须使用符合 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());
   }
}

试过这个,它奏效了!不过有一个提示,我必须重建您的项目才能使这些编译器配置生效。
这可以按照以下说明在 Gradle 中设置:stackoverflow.com/a/37482148/3854385
M
Markus Jevring

请参阅 java.beans.ConstructorProperties,它是专为执行此操作而设计的注释。


f
flybywire

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

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

取自 spring documentation


org.springframework.core.Conventions.getVariableName() 但我想你必须有 cglib 作为依赖
W
WhyNotHugo

虽然这是不可能的(正如其他人所说明的那样),但您可以使用注释来传递参数名称,并通过反射获得它。

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


t
tim_yates

所以你应该能够做到:

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

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

["int : arg0"]

I believe this will be fixed in 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
        }

我也不想指定方法 aMethod 的名称。我想为类中的所有方法获取它。
@snoop 添加了获取每个非合成方法的示例
我们不能使用 antlr 来获取参数名称吗?
C
Community

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

https://i.stack.imgur.com/RSLra.png


O
OneCricketeer

您无法说出使用的参数的名称。

但是,您可以使用反射检索方法签名并检测其参数类型。检查getParameterTypes()


真的,这一切皆有可能。然而,他的问题是明确地关于参数名称。检查主题标题。
我引用:“但是,你不能说出所用参数的名称。”只需阅读我的答案-_-
这个问题不是以他不知道获得类型的方式提出的。
C
Community

正如@Bozho 所说,如果在编译期间包含调试信息,则可以这样做。这里有一个很好的答案...

How to get the parameter names of an object's constructors (reflection)?@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 的原始解决方案,使其适用于方法。如果我正确理解,他的解决方案仅适用于构造函数。

此解决方案不适用于静态方法。这是因为在这种情况下,ASM 返回的参数数量不同,但它可以很容易地修复。


d
danben

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

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


参数名称不仅对编译器有用,它们还向库的使用者传达信息,假设我编写了一个 StrangeDate 类,如果你使用它并找到一个方法,它有方法 add(int day,int hour, int minute) add(int arg0, int arg1, int arg2) 这会有多大用处?
对不起 - 你完全误解了我的回答。请在问题的上下文中重新阅读。
获取参数的名称对于反射调用该方法有很大的好处。它不仅对编译器有用,甚至在问题的上下文中也是如此。
Parameter names are only useful to the compiler. 错误。查看改造库。它使用动态接口来创建 REST API 请求。它的功能之一是能够在 URL 路径中定义占位符名称并将这些占位符替换为相应的参数名称。
E
Elister

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

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


T
Tires

从 Java 字节码中读取附加符号信息的一种简单方法是:

Reflector reflector = new Reflector();
JavaMethod method = reflector.reflect(Whatever.class)
    .getMethods()
    .stream()
    .filter(m -> "aMethod".equals(m.getName()))
    .findFirst()
    .get();
String paramName = method.getParameters().getVariables().get(0).getName();
System.out.println(paramName);

从 Maven 中央工件:

<dependency>
    <groupId>com.intersult</groupId>
    <artifactId>coder</artifactId>
    <version>1.5</version>
</dependency>