ChatGPT解决这个技术问题 Extra ChatGPT

Can I obtain method parameter name using Java reflection?

If I have a class like this:

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

is there any way to know that aMethod uses a parameter named aParam, that is of type int?

7 answers and nobody mentioned the term "method signature". This is basically what you can introspect with reflection, which means: no parameter names.
it is possible, see my answer below
6 years later, it's now possible via reflection in Java 8, see this SO answer

b
bernie

In Java 8 you can do the following:

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

So for your class Whatever we can do a manual test:

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

which should print [aParam] if you have passed -parameters argument to your Java 8 compiler.

For Maven users:

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

For more information see following links:

Official Java Tutorial: Obtaining Names of Method Parameters JEP 118: Access to Parameter Names at Runtime Javadoc for Parameter class


I don't know if they changed the arguments for the compiler plugin, but with the latest (3.5.1 as of now) I had to do to use compiler args in the configuration section: -parameters
To pass parameters for Gradle, follow these instructions: stackoverflow.com/a/37482148/3854385
C
Community

To summarize:

getting parameter names is possible if debug information is included during compilation. See this answer for more details

otherwise getting parameter names is not possible

getting parameter type is possible, using method.getParameterTypes()

For the sake of writing autocomplete functionality for an editor (as you stated in one of the comments) there are a few options:

use arg0, arg1, arg2 etc.

use intParam, stringParam, objectTypeParam, etc.

use a combination of the above - the former for non-primitive types, and the latter for primitive types.

don't show argument names at all - just the types.


Is this possible with Interfaces? I still could not find a way to get interface parameter names.
@Cemo : were you able to find a way to get interface parameter names ?
Just to add, that's why spring needs @ConstructorProperties annotation for un-ambiguously creating object from primitive types.
S
Sarel Botha

The Paranamer library was created to solve this same problem.

It tries to determine method names in a few different ways. If the class was compiled with debugging it can extract the information by reading the bytecode of the class.

Another way is for it to inject a private static member into the bytecode of the class after it is compiled, but before it is placed in a jar. It then uses reflection to extract this information from the class at runtime.

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

I had problems using this library, but I did get it working in the end. I'm hoping to report the problems to the maintainer.


I am trying to use paranamer inside an android apk. But I am getting ParameterNAmesNotFoundException
D
Denis Danilkovich

see org.springframework.core.DefaultParameterNameDiscoverer class

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

K
Karol Król

Yes. Code must be compiled with Java 8 compliant compiler with option to store formal parameter names turned on (-parameters option). Then this code snippet should work:

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

Tried this and it worked! One tip though, I had to rebuild your project for these compiler configs to take effect.
This can be set in Gradle by following this instructions: stackoverflow.com/a/37482148/3854385
M
Markus Jevring

See java.beans.ConstructorProperties, it's an annotation designed for doing exactly this.


f
flybywire

It is possible and Spring MVC 3 does it, but I didn't take the time to see exactly how.

The matching of method parameter names to URI Template variable names can only be done if your code is compiled with debugging enabled. If you do have not debugging enabled, you must specify the name of the URI Template variable name in the @PathVariable annotation in order to bind the resolved value of the variable name to a method parameter. For example:

Taken from the spring documentation


org.springframework.core.Conventions.getVariableName() but I suppose that you must have cglib as dependency
W
WhyNotHugo

While it is not possible (as others have illustrated), you could use an annotation to carry over the parameter name, and obtain that though reflection.

Not the cleanest solution, but it gets the job done. Some webservices actually do this to keep parameter names (ie: deploying WSs with glassfish).


t
tim_yates

So you should be able to do:

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

But you'll probably get a list like so:

["int : arg0"]

I believe this will be fixed in Groovy 2.5+

So currently, the answer is:

If it's a Groovy class, then no, you can't get the name, but you should be able to in the future.

If it's a Java class compiled under Java 8, you should be able to.

See also:

http://openjdk.java.net/jeps/118

https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html

For every method, then something like:

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

I also don't want to specify the name of a method aMethod. I want to get it for all methods in a class.
@snoop added an example of getting every non-synthetic method
Cant we use antlr to get parameter names for this?
C
Community

if you use the eclipse, see the bellow image to allow the compiler to store the information about method parameters

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


O
OneCricketeer

You can't tell the name of the argument used.

You can retrieve the method signature with reflection and detect its argument types, however. Check getParameterTypes().


Truly that's all possible. His question was however explicitly about the parameter name. Check the topictitle.
And I quote: "However, you can't tell the name of the argument used." just read my answer -_-
The question wasn't formulated that way he didn't knew about obtaining the type.
C
Community

As @Bozho stated, it is possible to do it if debug information is included during compilation. There's a good answer here...

How to get the parameter names of an object's constructors (reflection)? by @AdamPaynter

...using the ASM library. I put together an example showing how you can achieve your goal.

First of all, start with a pom.xml with these dependencies.

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

Then, this class should do what you want. Just invoke the static method 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;
    }
}

Here's an example with a unit test.

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) {
        }

    }
}

You can find the complete example on GitHub

Caveats

I slightly changed the original solution from @AdamPaynter to make it work for Methods. If I properly understood, his solution works only with constructors.

This solution does not work with static methods. This is becasue in this case the number of arguments returned by ASM is different, but it something that can be easily fixed.


d
danben

Parameter names are only useful to the compiler. When the compiler generates a class file, the parameter names are not included - a method's argument list only consists of the number and types of its arguments. So it would be impossible to retrieve the parameter name using reflection (as tagged in your question) - it doesn't exist anywhere.

However, if the use of reflection is not a hard requirement, you can retrieve this information directly from the source code (assuming you have it).


Parameter name are not only useful to a compiler they also convey information to the consumer of a libary, assume I wrote a class StrangeDate which had method add(int day,int hour, int minute) if you went to use this and found a method add(int arg0, int arg1, int arg2) how useful would that be?
I'm sorry - you misunderstood my answer entirely. Please reread it in the context of the question.
Acquiring the names of parameters is a great benefit to calling that method reflectively. It is not only useful to the compiler, even in context of the question.
Parameter names are only useful to the compiler. Wrong. Look at Retrofit library. It uses dynamic Interfaces to create REST API requests. One of its features is the ability to define placeholder names in the the URL paths and replace those placeholders with their corresponding parameter names.
E
Elister

To add my 2 cents; parameter info is available in a class file "for debugging" when you use javac -g to compile the source. And it is available to APT but you'll need an annotation so no use to you. (Somebody discussed something similar 4-5 years ago here: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )

Overall in-short you can't get it unless you work on Source files directly (similar to what APT does at compile time).


T
Tires

One simple method to read additional symbol informations from Java bytecode is:

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

From Maven Central artifact:

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