31 July, 2011

How to extract type arguments from parameterized (generic) class

A nice thing about parameterized classes in Java is that you can extract and access information about class type arguments at runtime (for an explanation about parameters and arguments see Parameters and arguments). Let’s say that we have an abstract parameterized superclass that has a method which provides some common functionality to all its subclasses. And inside that method we wanted to know what is the class type argument specified by the subclass.

public abstract class GenericAbstractSuperclass<T> {
    public void foo() {
        // here we wanted to know what is the class type argument
    }
}

public class GenericClass1 extends GenericAbstractSuperclass<Integer> {
    // subclass methods
}

public class GenericClass2 extends GenericAbstractSuperclass<String> {
    // subclass methods
}

In above example, method foo() has to access class type argument. In case of GenericClass1 it has to get Integer and in case of GenericClass2 it has to get String. Below is listed a utility class that can accomplish this.

public class Generics {
    private Generics() {
        // empty, in order to avoid instantiation
    }

    public static <T> Class<T> extractTypeArgumentFromClass(
        Class<?> parameterizedClass) throws IllegalArgumentException {
        return extractTypeArgumentFromClass(parameterizedClass, 0);
    }

    public static <T> Class<T> extractTypeArgumentFromClass(
        Class<?> parameterizedClass, int argumentNumber)
        throws IllegalArgumentException {
        ParameterizedType parameterizedType = extractParameterizedTypeFromClass(
            parameterizedClass);
        if (parameterizedType == null) {
            throw new IllegalArgumentException(
                    "Class and any of its superclasses are not parameterized.");
        }
        return (Class<T>) getActualTypeArgument(parameterizedType, argumentNumber);
    }

    private static Type getActualTypeArgument(ParameterizedType parameterizedType,
            int argumentNumber) throws IllegalArgumentException {
        Type[] typeArguments = parameterizedType.getActualTypeArguments();
        if (typeArguments.length == 0) {
            throw new IllegalArgumentException("Class is not parameterized.");
        }
        if (argumentNumber < 0 || typeArguments.length <= argumentNumber) {
            throw new IllegalArgumentException("Invalid argumentNumber.");
        }
        return typeArguments[argumentNumber];
    }

    public static ParameterizedType extractParameterizedTypeFromClass(
        Class<?> parameterizedClass) {
        Type genericSuperclass = parameterizedClass.getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
            return (ParameterizedType) genericSuperclass;
        }
        if (genericSuperclass != null) {
            return extractParameterizedTypeFromClass((Class<?>) genericSuperclass);
        }
        return null;
    }

    public static <T> Class<T> extractTypeArgumentFromInterface(
        Class<?> parameterizedClass) throws IllegalArgumentException {
        return extractTypeArgumentFromInterface(parameterizedClass, 0);
    }

    public static <T> Class<T> extractTypeArgumentFromInterface(
        Class<?> parameterizedClass, int argumentNumber)
        throws IllegalArgumentException {
        ParameterizedType parameterizedType = extractParameterizedTypeFromInterface(
            parameterizedClass);
        if (parameterizedType == null) {
            throw new IllegalArgumentException(
                    "Class and any of its interfaces are not parameterized.");
        }
        return (Class<T>) getActualTypeArgument(parameterizedType, argumentNumber);
    }

    private static ParameterizedType extractParameterizedTypeFromInterface(
            Class<?> parameterizedClass) {
        Type[] genericInterfaces = parameterizedClass.getGenericInterfaces();
        for (Type genericInterface : genericInterfaces) {
            if (genericInterface instanceof ParameterizedType) {
                return (ParameterizedType) genericInterface;
            }
            ParameterizedType parameterizedType = extractParameterizedTypeFromInterface(
                (Class<?>) genericInterface);
            if (parameterizedType != null) {
                return parameterizedType;
            }
        }
        return null;
    }
}

This class provides methods for extracting type arguments from classes and from interfaces as well. The only use case which is not supported directly is when a class extends from a superclass that implements a parameterized interface. In this case, you will not be able to extract type argument from the class directly. Still, you can extract the class type argument using extractTypeArgumentFromInterface(parameterizedClass.getSuperclass()). Here, we first extracted the superclass and then we used it to extract the type argument from its interface.

Class source code can be found here. Unit tests source code can be found here. Unit tests represent also a good example of class usage.

Update (07-Aug-2011): Recently a new major release of Apache Commons Lang was released - Apache Commons Lang 3.0. Among new utility classes that were added, you will find a new TypeUtils class, which provides utility methods for working with generic types (including retrieval of type arguments from parameterized classes).

No comments:

Post a Comment