2

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

以下代码有效:

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
}

显然,问题在于通配符通用类型适配器不能保证处理 type 的对象clazz。但是,我不能通过将方法签名(我可能会这样做)更改为 来表明这两个是相同的private final static <T> String serialize(Object obj, Map<Class<T>, XmlAdapter<?,T>> classToAdapterMap),因为映射需要保存所有不同类型的适配器。

有什么更好的方法来做到这一点?还是我应该坚持使用基于反射的解决方案?

提前致谢,

-担

4

2 回答 2

1

有几种解决方案可以规避这个问题。

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

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

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

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

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

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

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
}

语义正确性来自以下几点:

  • 你可能认为T是实际类,obj因为T是一个方法绑定参数,没有在签名的其他地方使用;
  • clazzT我们检查后的类型的超类型clazz.isInstance(obj)
  • adapter可以处理它的实例clazz或超类型,因为它是地图的构建方式;
  • 因此,adapter可以处理(未知)超类型的所有实例T,因此? super T声明。
于 2015-04-25T17:57:51.720 回答
1

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

首先,我们需要一个小的特殊化,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

综上所述,该解决方案为您提供以下优势:

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

你付出的代价是:

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

希望有帮助!

于 2015-04-25T14:29:24.500 回答