是否需要反射来动态地将正确的通用适配器应用到我的对象
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
}
语义正确性来自以下方面:
- 您可能认为
T
是 obj
的实际 class,因为 T
是方法绑定参数,未在签名的其他地方使用;
clazz
是 T
类型的超类型,因为我们检查了 clazz.isInstance(obj)
;
adapter
可以处理 clazz
或其超类型的实例,因为它是地图的构建方式;
- 因此,
adapter
可以处理 T
的(未知)超类型的所有实例,因此 ? super T
声明。
我目前正在研究一个使用通用类型适配器库的序列化例程。如果被序列化的对象是我拥有的特定适配器之一的实例,那么我需要在执行其他序列化过程之前对该对象调用该适配器。
以下代码有效:
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 classTalkingXmlAdapter
强制每个适配器使用泛型声明它们的正确类型。
您支付的价格是:
- 引入新的超级类型。
- 需要对您的自定义适配器进行小的调整。
希望对您有所帮助!
有几种解决方法可以避免这个问题。
最有可能,最简单的方法是 使用原始类型:不要为 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
}
语义正确性来自以下方面:
- 您可能认为
T
是obj
的实际 class,因为T
是方法绑定参数,未在签名的其他地方使用; clazz
是T
类型的超类型,因为我们检查了clazz.isInstance(obj)
;adapter
可以处理clazz
或其超类型的实例,因为它是地图的构建方式;- 因此,
adapter
可以处理T
的(未知)超类型的所有实例,因此? super T
声明。