简体   繁体   中英

How can I get the parameterized-type class from a parameterized-type method argument?

Consider this contrived class:

import java.util.List;
public class Test {
    public String chooseRandom(List<String> strings) {
        return null;
    }
}

When using reflection to inspect this method, how can I get the Class object representing java.lang.String (or even the string "java.lang.String" ) when looking at the arguments for chooseRandom ?

I know that Java erases types at compile-time, but they must still be in there, since javap can print them properly. Running javap on this compiled class results in:

Compiled from "Test.java"
public class Test {
  public Test();
  public java.lang.String chooseRandom(java.util.List<java.lang.String>);
}

The parameterized type for java.util.List ( java.lang.String ) is definitely available... I just can't find out what it is.

I've tried this:

Class clazz = [grab the type of chooseRandom's parameter list's first argument];
String typeName = clazz.getName();
TypeVariable[] genericTypes = clazz.getTypeParameters();
if(null != genericTypes && 0 < genericTypes.length)
{
    boolean first = true;
    typeName += "<";
    for(TypeVariable type : genericTypes)
    {
        if(first) first = false;
        else typeName += ",";

        typeName += type.getTypeName();
    }

    typeName = typeName + ">";
}

This gives me a typeName of java.util.List<E> . What I'm hoping for is java.util.List<java.lang.String> .

I've read a few other questions and answers on SO and none of them really seem to answer this question, or if they do, there is no working code associated with them and the explanations are ... thin.

Use the getGenericParameterTypes and getActualTypeArguments methods:

Method m = Test.class.getMethod("chooseRandom", List.class);
Type t = m.getGenericParameterTypes()[0];
// we know, in this case, that t is a ParameterizedType
ParameterizedType p = (ParameterizedType) t;
Type s = p.getActualTypeArguments()[0]; // class java.lang.String

In practice, if you didn't know that there was at least one parameter and it was generic and had one type argument, of course, you would have to add some checks.

Method.getGenericParameterTypes() will return a ParameterizedType for generic arguments, which can be inspected to extract its type arguments. So Test.class.getMethod("chooseRandom", List.class).getGenericParameterTypes() should return a single-element array containing a ParameterizedType .

Java's type erasures mean that the "true type" of the parameter ( java.util.List ) doesn't contain the generic type information. You can't simply use Method.getParameterTypes()[i] and expect to extract the generic parameter types ( String , in this case) from there. Instead, you have to use Method.getGenericParameterTypes which returns a whole menagerie of implementations of the somewhat useless java.lang.reflect.Type interface.

Ultimately, here is code that gives an example of how to do everything I was trying to do:

public String getDataType(Class clazz)
{
    if(clazz.isPrimitive())
        return clazz.getName();

    if(clazz.isArray())
        return getDataType(clazz.getComponentType()) + "[]";

    String typeName;
    if("java.lang".equals(clazz.getPackage().getName()))
        typeName = clazz.getName().substring(10);
    else
        typeName = clazz.getName();

    return typeName;
}

public String getDataType(Type type)
{
    if(type instanceof Class)
        return getDataType((Class)type);

    if(type instanceof ParameterizedType)
    {
        ParameterizedType pt = (ParameterizedType)type;
        StringBuilder typeName = new StringBuilder(getDataType(pt.getRawType()));

        Type[] specificTypes = pt.getActualTypeArguments();
        if(null != specificTypes && 0 < specificTypes.length)
        {
            typeName.append("<");
            for(int j=0; j<specificTypes.length; ++j)
            {
                if(j > 0)
                    typeName.append(",");

                typeName.append(getDataType(specificTypes[j]));
            }

            typeName.append(">");
        }

        return typeName.toString();
    }

    return "[" + type + ", a " + type.getClass().getName() + "]";
}

public void getMethodSignature(Method m)
{
    System.out.print(getDataType(m.getGenericReturnType());
    System.out.print(" ");
    System.out.print(method.getName());
    System.out.print("(");
    Type[] parameterTypes = method.getGenericParameterTypes();
    if(null != parameterTypes && 0 < parameterTypes.length)
    {
        for(int i=0; i<parameterTypes.length; ++i)
        {
            if(0<i) System.out.print(",");
            System.out.print(getDataType(parameterTypes[i]));
        }
    }
    System.out.println(")");
}

I've left-out the parts of the signature that aren't that exciting and would just clutter things up: visibility and other flags, the exception types, etc.

The above code can properly decode the type signature of the real method I was trying to plumb for more information:

protected void parseLocalesHeader(String string,
                                  java.util.TreeMap<Double,java.util.ArrayList<java.util.Locale>> treeMap)

Note that this currently doesn't handle a few implementations of Type , such as TypeVariable (that's the one that looks like <T extends Serializable> ). I'll leave that as an exercise for anyone who wants to follow in my footsteps. The above code should get you on your way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM