21

Say I somehow got an object reference from an other class:

Object myObj = anObject;

Now I can get the class of this object:

Class objClass = myObj.getClass();

Now, I can get all constructors of this class:

Constructor[] constructors = objClass.getConstructors();

Now, I can loop every constructor:

if (constructors.length > 0)
{
    for (int i = 0; i < constructors.length; i++)
    {
        System.out.println(constructors[i]);
    }
}

This is already giving me a good summary of the constructor, for example a constructor public Test(String paramName) is shown as public Test(java.lang.String)

Instead of giving me the class type however, I want to get the name of the parameter.. in this case "paramName". How would I do that? I tried the following without success:

if (constructors.length > 0)
    {
        for (int iCon = 0; iCon < constructors.length; iCon++)
        {
            Class[] params = constructors[iCon].getParameterTypes();
            if (params.length > 0)
            {
                for (int iPar = 0; iPar < params.length; iPar++)
                {
                    Field fields[] = params[iPar].getDeclaredFields();
                    for (int iFields = 0; iFields < fields.length; iFields++)
                    {
                        String fieldName = fields[i].getName();
                        System.out.println(fieldName);
                    }                                       
                }
            }
        }
    }

Unfortunately, this is not giving me the expected result. Could anyone tell me how I should do this or what I am doing wrong? Thanks!

Tom
  • 8,536
  • 31
  • 133
  • 232
  • 1
    This is possible via reflection in **Java 8**, see [this SO answer](http://stackoverflow.com/a/21455958/573057) - found by reading the documentation on [paranamer](https://github.com/paul-hammant/paranamer) from Duncan McGregor's answer below. – earcam Jun 28 '16 at 20:37

3 Answers3

20

As mentioned in the comments on Roman's answer, the parameter names can be retrieved if the compiler included debugging symbols, though not through the standard Java Reflection API. Below is an example illustrating how you could obtain parameter names via the debugging symbols using the ASM bytecode library:

/**
 * 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 constructor
 * @return 
 * @throws IOException
 */
public static List<String> getParameterNames(Constructor<?> constructor) throws IOException {
    Class<?> declaringClass = constructor.getDeclaringClass();
    ClassLoader declaringClassLoader = declaringClass.getClassLoader();

    Type declaringType = Type.getType(declaringClass);
    String constructorDescriptor = Type.getConstructorDescriptor(constructor);
    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("<init>") && 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 = 0; i < argumentTypes.length; i++) {
                // The first local variable actually represents the "this" object
                parameterNames.add(localVariables.get(i + 1).name);
            }

            return parameterNames;
        }
    }

    return null;
}

This example uses the ASM library's tree API. If speed and memory are precious, you can refactor the example to use its visitor API instead.

Community
  • 1
  • 1
Adam Paynter
  • 46,244
  • 33
  • 149
  • 164
14

This information is lost after compilation and can't be retrieved at runtime.

Roman
  • 64,384
  • 92
  • 238
  • 332
  • Are you sure? So a constructor is treated differently than other methods? – Tom Apr 28 '10 at 13:04
  • 1
    @Tom: Neither constructors nor methods retain parameter names after compilation. – Adam Paynter Apr 28 '10 at 13:05
  • At http://stackoverflow.com/questions/471693/using-reflection-to-get-method-name-and-parameters Jon Skeet says in an answer "You can't get method parameter values from reflection. You can get the parameter names and types, but not the parameters themselves." - this is what made me believe that it was possible, but I guess he was wrong. Thanks though. – Tom Apr 28 '10 at 13:08
  • 2
    @Tom: Jon is referring to the debugging symbols that compilers may optionally include in the class files. In that sense, parameter names are *technically* available, though not through the reflection API. – Adam Paynter Apr 28 '10 at 13:11
  • 1
    Alright, thanks. Accepting this answer. – Tom Apr 28 '10 at 13:13
  • @Tom: That was a C# question. This is a Java question. – Jon Skeet Jun 15 '10 at 19:16
  • 3
    For java 8, see: https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html – de-bits Jan 16 '18 at 21:12
13

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

Oh for goodness sake SO, really, you're going to make me enter at least 30 characters to edit an existing answer to make it correct.

Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118