Search code examples
javagenericsreflectionpolymorphismxmladapter

Is Reflection needed to apply the correct generic adapter to my object dynamically


I am currently working on a serialization routine which uses a library of generically typed adapters. If the object being serialized is an instance of one of the specific adapters I have, then I need to call that adapter on the object prior to performing my other serialization procedures.

The following code works:

private final static String serialize(Object obj, Map<Class<?>, 
        XmlAdapter<?,?>> classToAdapterMap) throws JAXBException 
{
    Object adaptedObj = null;

    for (Class<?> clazz : classToAdapterMap.keySet()) {
        if (clazz.isInstance(obj)) {
            XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
            Class<?>[] argTypes = new Class[] {clazz};
            try {
                Method method = adapter.getClass().getMethod("marshal", argTypes);
                adaptedObj = method.invoke(adapter, obj);
                break;
            } catch (Exception e) {
                // handle method retrieval and invocation related exceptions
            }
        }
    }

    // serialize
}

However, I had originally thought that I would be able to do this more simply, for example with code like:

/* DOES NOT WORK */
private final static String serialize(Object obj, Map<Class<?>, 
        XmlAdapter<?,?>> classToAdapterMap) throws JAXBException 
{
    Object adaptedObj = null;

    for (Class<?> clazz : classToAdapterMap.keySet()) {
        if (clazz.isInstance(obj)) {
            XmlAdapter<?,?> adapter = classToAdapterMap.get(clazz);
            adaptedObj = adapter.marshal(clazz.cast(obj));
            break;
        }
    }

    // serialize
}

Clearly the problem is that the wildcard generically typed adapter isn't guaranteed to handle an object of type clazz. However, I can't indicate that these two are the same by changing the method signature—as I might otherwise do—to private final static <T> String serialize(Object obj, Map<Class<T>, XmlAdapter<?,T>> classToAdapterMap), because the map needs to hold adapters of all different types.

What would be a better way to do this? Or should I stick with the Reflection based solution?

Thanks in advance,

-Dan


Solution

  • There are several solutions to circumvent this problem.

    Most likely, the easiest one is using raw types: don't specify the type parameters for the adapter, and the compiler will happily accept the marshall call (with a raw type warning of course):

    XmlAdapter adapter = classToAdapterMap.get(clazz);
    adaptedObj = adapter.marshal(obj);
    

    (This is actually roughly the same solution as Bastian's, without the intermediate type)

    If you don't like raw types, you may choose the unchecked cast to an Object-parameterized adapter. It's not really better, but it also works (by tricking the compiler…):

    XmlAdapter<?, Object> adapter = (XmlAdapter<?, Object>) classToAdapterMap.get(clazz);
    adaptedObj = adapter.marshal(obj);
    

    My last solution is to use a type parameter at the method level. This time, what you do is semantically correct (as long as the map itself is correct), and the unchecked cast really means “I know what I am doing here”:

    private final static <T> String serialize(T obj, Map<Class<?>, 
            XmlAdapter<?,?>> classToAdapterMap) throws JAXBException 
    {
        Object adaptedObj = null;
    
        for (Class<?> clazz : classToAdapterMap.keySet()) {
            if (clazz.isInstance(obj)) {
                try {
                    XmlAdapter<?, ? super T> adapter = (XmlAdapter<?, ? super T>) classToAdapterMap.get(clazz);
                    adaptedObj = adapter.marshal(obj);
                    break;
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    
        // serialize
    }
    

    The semantic correctness comes from the following:

    • you may consider T to be the actual class of obj since T is a method-bound parameter, not used elsewhere in the signature;
    • clazz is a super type of the type of T since we checked clazz.isInstance(obj);
    • adapter can handle instances of clazz or a super-type of it since it is how the map was built;
    • by consequent, adapter can handle all instances of an (unknown) super type of T, hence the ? super T declaration.