0

I need a recursive function, that would print a class signature, including class name and values uf type parameters.

MyNewClass< String,List< Integer>,Map< List< Integer>,String>> x = new MyNewClass<...>(); Assert.assert(getSimpleNameWithGenerics(x).equals("MyNewClass< String,List< Integer>,Map< List< Integer>,String>>"));

Since I'm a newbie in Java, I was quite surprised, not to find such function anywhere around Java community, nor to be able to implement it easily myself.

Here is my try:

public static String getGenericClassSimpleName(Class< ?> c) {

String s = c.getSimpleName();
TypeVariable[] tv = c.getTypeParameters();
for(int i = 0; i < tv.length; i++) {
        s += i == 0 ? "<" : ",";
        s += getGenericClassSimpleName(tv[i].getGenericDeclaration().getClass());
}
if(tv.length > 0) s += ">";
return s; 

}

, but it ends up with stack overflow (that's why I decided to ask here, lol), bumping the TypeVariable class... I never found the way to get to real "faces" of the type variables.

Community
  • 1
  • 1
Andrey
  • 333
  • 3
  • 5
  • 16
  • 2
    This has been asked more than a couple of times, try searching for "java type erasure" or something. Bottom line is, after compile-time, all the JVM knows is the lower and upper bound of the generic (i.e. if it's `` you can figure out the fact that it's at least an `Abc`, but when it is declared with just 'T' there's absolutely no usable info available, as the class of the generic is simply Object). – falstro Jan 16 '11 at 18:06
  • That's for compile-time, but I need for run-time. – Andrey Jan 16 '11 at 18:07
  • That's what I'm saying, the JVM doesn't know.. If it's declared ``, you can programmatically figure out that the lower bound is `Abc`, but the instance (x in your case) does not carry information about the actual type. So chances are, the best possible result you can get (for your example) from code is "MyNewClass" depending on how the *class* (not the instance) was declared. Instantiating an object of a generic in Java does not (unlike C++ template) instantiate the actual class again for a different type. – falstro Jan 16 '11 at 18:10
  • As *roe* indicates, Java does not have reified generics. Thus, what you wish to do is not possible. See http://stackoverflow.com/questions/879855/what-are-reified-generics-how-do-they-solve-the-type-erasure-problem-and-why-can for a good overview. – Brian Clapper Jan 16 '11 at 18:15
  • Ok, thanks! Looks like Java's generics are not that mature. I was hoping to provide my logger with more specific info about log entry author... – Andrey Jan 16 '11 at 18:28

2 Answers2

0

You have to have a field or a method that uses the type you are interested in, and then you can 'reflect' upon it and see all the generic details.

Or you can start from the type capture technology in Jackson and dig down from there to the library that supports it.

bmargulies
  • 97,814
  • 39
  • 186
  • 310
0

class can have access to its type parameters, Fields parameters and Method parameters. I use this piece of code to extract actual type parameters (it is simplified -".get...()[0]" since I only have a need to extract first type parameter).

private Class extractClass(Type type) {
    Class result = null;
    if (type instanceof Class)
        result = (Class) type;
    if (type instanceof WildcardType) {
        if (((WildcardType) type).getLowerBounds().length > 0) {
            result = extractClass(((WildcardType) type).getLowerBounds()[0]);
        } else if (((WildcardType) type).getUpperBounds().length > 0) {
            result = extractClass(((WildcardType) type).getUpperBounds()[0]);
        }
    } else if (type instanceof ParameterizedType) {
        result = extractClass(((ParameterizedType) type).getActualTypeArguments()[0]);
    } else if (type instanceof TypeVariable) {
        TypeVariable tv = (TypeVariable) type;

        Class c = getEntityClass();
        result = extractTypeVariableBounds(tv, c);
        if (result == null)
            result = extractTypeVariableBounds(tv, (Class) tv.getGenericDeclaration());
        if (result == null && tv.getBounds().length > 0)
            result = extractClass(tv.getBounds()[0]);

    }
    return result;
}

private Class extractTypeVariableBounds(TypeVariable tv, Class c) {
    if (c == null)
        return Object.class;

    Type genericSuperclass = c.getGenericSuperclass();
    if (genericSuperclass != null) {
        int index = 0;
        for (TypeVariable dtv : c.getSuperclass().getTypeParameters()) {
            if (dtv.equals(tv)) {
                while (genericSuperclass instanceof Class) {
                    genericSuperclass = ((Class) genericSuperclass).getGenericSuperclass();
                }
                if (genericSuperclass != null)
                    return extractClass(((ParameterizedType) genericSuperclass).getActualTypeArguments()[index]);
            }
            index++;
        }
        c = c.getSuperclass();
        return extractTypeVariableBounds(tv, c.getSuperclass());
    }
    if (tv.getBounds().length > 0)
        return extractClass(tv.getBounds()[0]);
    return Object.class;
}

See this class: AEntityAccessor.java and PropertyAccessor.java to see how its used to extract Map parameters.

it's not a direct answer but still, I hope You will find this useful.

Tomasz Krzyżak
  • 1,087
  • 8
  • 10