Java 是否需要反射将正确的泛型适配器动态应用于我的对象

Java 是否需要反射将正确的泛型适配器动态应用于我的对象,java,generics,reflection,polymorphism,xmladapter,Java,Generics,Reflection,Polymorphism,Xmladapter,我目前正在开发一个使用泛型适配器库的序列化例程。如果要序列化的对象是我拥有的某个特定适配器的实例,那么我需要在执行其他序列化过程之前调用该对象上的适配器 以下代码起作用: private final static String serialize(Object obj, Map<Class<?>, XmlAdapter<?,?>> classToAdapterMap) throws JAXBException { Object ad

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

以下代码起作用:

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
}
private final静态字符串序列化(对象obj,映射类别:classToAdapterMap.keySet()){
if(类实例(obj)){
XmlAdapter=classToAdapterMap.get(clazz);
类[]argTypes=新类[]{clazz};
试一试{
Method=adapter.getClass().getMethod(“封送”,argTypes);
adaptedbj=method.invoke(适配器,obj);
打破
}捕获(例外e){
//处理方法检索和调用相关的异常
}
}
}
//连载
}
然而,我最初认为我可以更简单地完成这项工作,例如使用以下代码:

/* 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
}
/*不起作用*/
私有最终静态字符串序列化(对象obj,映射类别:classToAdapterMap.keySet()){
if(类实例(obj)){
XmlAdapter=classToAdapterMap.get(clazz);
adaptedbj=adapter.marshal(clazz.cast(obj));
打破
}
}
//连载
}

显然,问题在于通配符泛型适配器不能保证处理
clazz
类型的对象。但是,我不能通过改变方法签名来表明这两个是相同的,因为我可能会改变方法签名来进行
private final static String serialize(Object obj,Map有一种更简单、更安全的方法,不使用反射:

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

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

    public abstract Class<BoundType> getBoundType();

}
这使我们能够编写简化的序列化方法:

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();
    }

}
综上所述,该解决方案具有以下优势:

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

  • 引入一种新的超级类型
  • 需要对自定义适配器进行小的自适应

希望这会有所帮助!

有几种解决方案可以避免此问题

最容易的方法是使用原始类型:不要为
适配器
指定类型参数,编译器将愉快地接受
marshall
调用(当然还有原始类型警告):

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

如果您不喜欢原始类型,您可以选择未选中强制转换
对象
-参数化适配器。这并不是更好,但也可以工作(通过欺骗编译器…):

XmlAdapter,
XmlAdapter>classToAdapterMap)抛出JAXBEException
{
对象adaptedbj=null;
for(类clazz:classToAdapterMap.keySet()){
if(类实例(obj)){
试一试{

XmlAdapterThanks Bastian。使用一个子类XMLoad的列表,它添加方法来报告绑定和值类型实际上是我在制作这个帖子后所解决的一个方法。。不过,我想提一下,
adapter.getBoundType().equals(obj.getClass())
和我确信您的意思是
adapter.getBoundType().isInstance(obj)之间有一个非常重要的区别
。后者允许我使用类型化的适配器来处理正在序列化的实际对象的超类。这对于我的实际用例非常重要。它确实提出了可能的问题(我在文章中对此进行了讨论)如果多个适配器处理不同的超类,那么行为是未定义的,但是我通过选择适配器来避免这种情况
提高了灵活性。我调整了我的解决方案。谢谢您的评论。谢谢Didier。这是对可用选项的一个很好的总结。最后,我使用了您的第三个解决方案,将类型参数添加到序列化方法中,并结合Bastian关于子类化XmlAdapter的建议。(发布这个问题后不久,我意识到在这里使用映射并没有什么好处,因为所需适配器的绑定类型可能是一个超类,而不是与被序列化对象的类型匹配。因此,能够报告其类型的适配器列表对我来说似乎更清晰。)
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
XmlAdapter adapter = classToAdapterMap.get(clazz);
adaptedObj = adapter.marshal(obj);
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
}