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

如果我有这样一门课:

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

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

158283 次浏览

不能说出所使用的参数的名称。

但是,您可以通过反射检索方法签名并检测其 参数类型

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

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

总结一下:

  • 如果在编译过程中包含调试信息,则可以获得参数名
  • 否则得到参数名称是 没有可能的
  • 可以使用 method.getParameterTypes()获取参数类型

为了编写编辑器的自动完成功能(正如您在注释中所说的) ,有几个选项:

  • 使用 arg0arg1arg2等。
  • 使用 intParamstringParamobjectTypeParam等。
  • 使用上述两种方法的组合——前者用于非基元类型,后者用于基元类型。
  • 根本不显示参数名称——只显示类型。

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

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

这是可能的,SpringMVC3做到了这一点,但我没有花时间看到具体如何。

方法参数名称的匹配 到 URI 模板变量名可以 只有在编译代码时才能完成 启用调试。如果你有 未启用调试,则必须 指定 URI 模板的名称 中的变量名 注释,以便绑定 变量名的解析值设置为 一个方法参数,例如:

拍自 春季文件

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

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

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

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

我在使用这个图书馆时遇到了一些问题,但最终我还是让它运行起来了。我希望向维护人员报告这些问题。

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

虽然不是最干净的解决方案,但能完成任务。一些 Web 服务实际上这样做是为了保留参数名(例如: 使用 glassfish 部署 WS)。

请参阅 Bean 构造函数属性,它是一个专门为此设计的注释。

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

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参数传递给 Java8编译器,它应该打印 [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>

有关详情,请参阅以下连结:

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

参见 org.springframework.core. DefaultParameter terNameDiscover 类

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

是的。
代码 必须使用符合 Java8的编译器进行编译,带有存储打开的形式参数名称的选项(参数选项)。
然后这段代码应该可以工作:

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

因此,你应该能够做到:

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

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

["int : arg0"]

我相信这个问题在 Groovy 2.5 + 中会得到解决

因此,目前的答案是:

  • 如果它是一个 Groovy 类,那么不,您不能得到名称,但是将来应该能够得到。
  • 如果它是在 Java8下编译的 Java 类,那么您应该能够。

参见:


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

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

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

enter image description here

正如@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 的原始解决方案,使其适用于 Method。如果我理解正确的话,他的解决方案只适用于构造函数。
  • 此解决方案不适用于 static方法。这是因为在这种情况下,ASM 返回的参数数量是不同的,但是可以很容易地修复。

从 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);

来自玛文中心的艺术品:

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