Finding Generic Types with Java Reflection

Sometimes you need to access type information as Java programs execute. Accessing a field’s type, for instance, is necessary to invoke a setter or getter for that field at run time. And obtaining types for a method’s parameters and return value let you invoke that method “on-the-fly” when needed.

In this post, I show you how to generate strings at run time representing type information for Java class fields, methods, and constructors. This technique lets you gather type information all at once instead of having to call separate APIs. Strings are also easy to parse, so you can extract specific type information if you need to. Since types can be generic, I’ll show you how to generate string type information for parameterized types, too.

Non-Generic Types

Let’s begin with a Thing class that has no generic types.

class Thing {
    private int number;
    public Thing(int number) {
        this.number = number;
    }
    public void setNumber(int number) {
        this.number = number;
    }
    public int getNumber() {
        return number;
    }
}

The following statements access type information for the integer number field.

Field[] fields = Thing.class.getDeclaredFields();
for (Field field : fields)
    System.out.println(field);

Although class Thing has only one field, this code accesses all the Java class fields and is therefore a more general solution. The println() statement shows the type information for each field.

Here’s the output from this code.

private int Thing.number

You see the field’s visibility (private), its type (int), and the class that the field belongs to (Thing). Note that all type information for this field is stored in a Java string. That’s because the println() we use is really the following.

System.out.println(field.toString());

Therefore, calling toString() with a Field object generates a string with all the type information for that Java field.

Accessing a method’s type information is done in a similar way.

Method[] methods = Thing.class.getDeclaredMethods();
for (Method method : methods)
    System.out.println(method);

Here’s the output from this code.

public int Thing.getNumber()
public void Thing.setNumber(int)

This shows the visibility, return type, and signature of both methods along with the class that each method belongs to. Like before, the println() shown is actually

System.out.println(method.toString());

Thus, calling toString() with a Method object generates a string with all the type information for that Java method.

Not surprisingly, type information for a constructor is done in a similar fashion.

Constructor<?>[] constrs = Thing.class.getDeclaredConstructors();
for (Constructor<?> ctor : constrs)
    System.out.println(ctor);

Here’s the output from this code, which shows the constructor’s name, visibility, and signature.

public Thing(int)

One again, calling toString() with a Constructor<?> object generates a string with all the type information for that Java constructor.

System.out.println(ctor.toString());

Generic Types

Java types can be generic, so let’s discuss this next. Here’s a MyThing class with fields, methods, and constructors that all have generic types.

class MyThing {
    private Map<String, Integer> entries;
    public MyThing(Map<String, Integer> entries) {
        this.entries = entries;
    }
    public void storeValues(ArrayList<Float> values)  {  }
    public Map<String, Integer> getEntries() {
        return entries;
    }
}

Can we use the same approach with implicit toString() methods to display type information for this class?

If you do, here’s the output.

private java.util.Map MyThing.entries
public void MyThing.storeValues(java.util.ArrayList)
public java.util.Map MyThing.getEntries()
public MyThing(java.util.Map)

Do you notice something missing?

In each line, only the raw types (java.util.Map and java.util.ArrayList) appear for these generic collection classes. Their parameterized types (<String, Integer> for Map and <Float> for ArrayList) are omitted. This type information does not appear for the collection classes, and that’s important missing information.

To access both raw and parameterized types, you must call toGenericString() with your Field, Method, and Constructor<?> objects. (Note that toString() is the default, so an explicit call is necessary.)

The correct code to show all generic type information for fields, methods, and constructors is the following.

System.out.println(field.toGenericString());
System.out.println(method.toGenericString());
System.out.println(ctor.toGenericString());

When you do this, parameterized types now appear with the generic collection classes.

private java.util.Map<java.lang.String, java.lang.Integer> MyThing.entries
public void MyThing.storeValues(java.util.ArrayList<java.lang.Float>)
public java.util.Map<java.lang.String, java.lang.Integer> MyThing.getEntries()
public MyThing(java.util.Map<java.lang.String, java.lang.Integer>)

Wrap Up

In this post, I showed you how to generate strings at run time representing type information for Java class fields, methods, and constructors. You also learned that toGenericString() is necessary to access all the type information for generic types.

As a good rule of thumb, always invoke toGenericString() with Field, Method, and Constructor<?> objects. This method generates correct type information as strings for both generic and non-generic types.

If you’d like to learn more about Java Reflection and other techniques like these, check out my JavaReflection LiveLesson.

Java Reflection Live Lesson

Java Reflection Live Lesson