是否需要反射来动态地将正确的通用适配器应用到我的对象

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

我目前正在研究一个使用通用类型适配器库的序列化例程。如果被序列化的对象是我拥有的特定适配器之一的实例,那么我需要在执行其他序列化过程之前对该对象调用该适配器。

以下代码有效:

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
}

然而,我原本以为我可以更简单地做到这一点,例如使用如下代码:

/* 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
}

很明显,问题是通配符泛型类型适配器不能保证处理 clazz 类型的对象。但是,我无法通过将方法签名更改为 private final static <T> String serialize(Object obj, Map<Class<T>, XmlAdapter<?,T>> classToAdapterMap) 来表明这两者相同,因为映射需要包含所有不同类型的适配器。

执行此操作的更好方法是什么?还是我应该坚持使用基于反射的解决方案?

提前致谢,

-丹

有一种不使用反射更简单更安全的方法:

首先,我们需要对 XmlAdapter 进行一些专门化,因为它允许我们向适配器询问它可以处理的类型。

public abstract class TalkingXmlAdapter<ValueType, BoundType> extends XmlAdapter<ValueType, BoundType> {

    public abstract Class<BoundType> getBoundType();

}

我的自定义适配器现在需要扩展 TalkingXmlAdapter:

public class AppleXmlAdapter extends TalkingXmlAdapter<String, Apple> {

    @Override
    public Class<Apple> getBoundType() {
        return Apple.class;
    }

    @Override
    public Apple unmarshal(String v) throws Exception {
        System.out.println("Unmarshalling Apple");
        return new Apple();
    }

    @Override
    public String marshal(Apple v) throws Exception {
        System.out.println("Marshalling Apple");
        return "Apple";
    }

}


public class BananaXmlAdapter extends TalkingXmlAdapter<String, Banana> {

    @Override
    public Class<Banana> getBoundType() {
        return Banana.class;
    }

    @Override
    public Banana unmarshal(String v) throws Exception {
        System.out.println("Unmarshalling Banana");
        return new Banana();
    }

    @Override
    public String marshal(Banana v) throws Exception {
        System.out.println("Marshalling Banana");
        return "Banana";
    }

}

这让我们可以编写一个简化的序列化方法:

public class SimpleSerializer {

    public static final String serialize(Object obj, List<TalkingXmlAdapter> allAdapters) throws Exception {
        Object adaptedObj = null;

        for (TalkingXmlAdapter adapter : allAdapters) {
            if (adapter.getBoundType().isInstance(obj)) {
                adaptedObj = adapter.marshal(obj);
                break;
            }
        }
        // serialize
        System.out.println("Simple serializing for " + obj.toString());
        return "Simply serialized " + obj.toString();
    }

}

使用代码,例如就像在随后的清单中显示了您想要的行为:

    List<TalkingXmlAdapter> allAdapters = new ArrayList<>();
    allAdapters.add(new AppleXmlAdapter());
    allAdapters.add(new BananaXmlAdapter());

    SimpleSerializer.serialize(new Banana(), allAdapters);
    SimpleSerializer.serialize("Lemmon", allAdapters);
    SimpleSerializer.serialize(new Apple(), allAdapters);

输出:

Marshalling Banana
Simple serializing for generic.adapter.Banana@659e0bfd
Simple serializing for Lemmon
Marshalling Apple
Simple serializing for generic.adapter.Apple@2a139a55

总而言之,该解决方案为您带来以下优势:

  • 您不需要简化代码的反射。
  • 您在序列化例程中需要更少的通用编程,这会简化您的代码。
  • 解决方案更安全。请注意,不需要类型转换。每个适配器都接受类型 Object。但是,通过使用通用方法 getBoundType(),您可以确保特定的运行时类型是正确的。在反射解决方案中构建地图时,错误映射的 class 会导致运行时异常。在提议的解决方案中,super class TalkingXmlAdapter 强制每个适配器使用泛型声明它们的正确类型。

您支付的价格是:

  • 引入新的超级类型。
  • 需要对您的自定义适配器进行小的调整。

希望对您有所帮助!

有几种解决方法可以避免这个问题。

最有可能,最简单的方法是 使用原始类型:不要为 adapter 指定类型参数,编译器将 愉快地 接受 marshall 调用(当然带有原始类型警告):

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

(这实际上和Bastian的解决方案大致相同,没有中间类型)

如果您不喜欢原始类型,您可以选择 未检查的强制转换Object 参数化适配器。它并不是真的更好,但它也可以工作(通过欺骗编译器......):

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

我最后的解决方案是在方法级别 使用类型参数。这一次,你所做的在语义上是正确的(只要地图本身是正确的),并且未经检查的转换真正意味着“我知道我在这里做什么”:

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
}

语义正确性来自以下方面:

  • 您可能认为 Tobj 的实际 class,因为 T 是方法绑定参数,未在签名的其他地方使用;
  • clazzT 类型的超类型,因为我们检查了 clazz.isInstance(obj);
  • adapter 可以处理 clazz 或其超类型的实例,因为它是地图的构建方式;
  • 因此,adapter 可以处理 T 的(未知)超类型的所有实例,因此 ? super T 声明。