我有一个名为InputCreator的界面。这是一个通用接口,而其具体实现确实知道相关类型。
interface InputCreator<T> {
public T createInput(String str) throws Exception;
}
其具体实现是XMLcreator。
class XMLCreator implements InputCreator<String> {
public String createInput(String str) throws Exception() {
// some code here
return xmlString;
}
}
由于XMLCreator使用类型参数String实现InputCreator,因此在编译时会检查方法签名。
现在可能有多个具体实现,所以我创建了带签名的静态工厂方法:
public static <T> InputCreator<T> createInputInstance(Type type) {
if (type == Type.XML)
return new XMLCreator();
else if (type == Type.SQL)
return new SQLCreator(); }
从这个方法我基于类型枚举创建XMLCreator对象。但是在返回此实例时,我收到编译器错误,因为它无法将XMLCreator
转换为InputCreator<T>
这就是我调用createInputInstance方法的方法。参数“inputType”的类型为enum。它有可能的值,如XML,SQL和其他一些自定义输入类型。这与类型参数T不同,因为对于XML我使用String类。所以发送类&lt; T&gt;因为参数没有帮助我。
InputCreator<String> iCreator = CreatorFactory.createInputInstance(inputType);
String input = iCreator.createInput(inputString);
我理解编译器无法解析T.的值 [编译器消息 - TypeMismatch:无法从XMLCreator转换为InputCreator&lt; T&gt;]
我该如何解决这个问题?
答案 0 :(得分:1)
我希望您没有任何特殊原因可以使用Type
作为工厂方法的参数。请改用Class
。不同之处在于Class
是自我参数化的,因此如果您将方法定义如下:
public static <T> InputCreator<T> createInputInstance(Class<T> clazz)
然后你可以使用它:
InputCreator<String> iCreator = CreatorFactory.createInputInstance(String.class);
既没有编译错误也没有警告。
答案 1 :(得分:0)
问题是,想想调用者将如何分配您从工厂返回的InputCreator
,
InputCreator<Integer> creator = CreatorFactory.createInstance("XML");
现在,由于类型推断,用户希望获得InputCreator<Integer>
,但您要返回XMLCreator
InputCreator<String>
,这不是用户认为的那样。
所以Java给你错误。
您所谈论的XML类型,SQL都是运行时检查,编译时间推断不会像这样工作。
答案 2 :(得分:0)
我不确定有什么方法可以解决这个问题。 “修复”是向工厂方法添加强制转换(和警告抑制):
public static <T> InputCreator<T> createInputInstance(Type t) {
switch (t) {
case XML:
return (InputCreator<T>) new XMLCreator();
// ...
}
}
答案 3 :(得分:0)
这是处理Java Generics时可能遇到的最明显的陷阱。 请记住,java泛型是通过 类型擦除 实现的。 这意味着关于通用参数(&lt; T&gt;)的所有信息将在编译时丢失。它们只是编译时类型安全的工具。 想象一下,您在运行时的界面转换为以下内容,然后您遇到返回类型的问题。
编译时间码:
interface InputCreator<T> {
public T createInput(String str) throws Exception;
}
转换为运行时间:
interface InputCreator {
public ?? createInput(String str) throws Exception;
}
因此问题。
一个类似但更明显的问题是想要在编译时实例化参数化类型的对象,如下面的代码所示:
class Foo<T> {
String bar() {
String s = (String) new T();
return s;
}
}
转换为运行时间:
class Foo {
String bar() {
String s = (String) new ??();
return s;
}
}
底线:
不要忘记在Java中,泛型是用类型擦除实现的(为了向后兼容),因此这是你必须付出的代价。您不能将它们用于返回类型或用于新建。
在其他语言(如C ++)中,您不需要支付这笔费用。
可以在http://www.ibm.com/developerworks/library/j-jtp01255/index.html
查看关于Java Generic Gotchas的好文章答案 4 :(得分:0)
您根本无法在不丢失类型安全的情况下将枚举与泛型混合使用。
但是如果你愿意放弃你的类型安全要求,那么实际的创建者类可以保留在你的Type
枚举中,你不需要静态的工厂方法(但是你需要它)一个显式的强制转换,因为你不能有通用的枚举实例):
enum Type {
XML(XMLCreator.class),
SQL(SQLCreator.class);
private final Class<? extends InputCreator<?> creatorType;
private Type(Class<? extends InputCreator<?> creatorType) {
this.creatorType = creatorType;
}
public <T> InputCreator<T> instantiateInputCreator() {
return (InputCreator<T>) creatorType.newInstance();
}
}
然后你可以像这样使用它:
InputCreator<String> inputCreator = Type.XML.instantiateInputCreator();
但是,您也可以这样做:
InputCreator<Integer> inputCreator = Type.XML.instantiateInputCreator();
如果您尝试使用新实例化的InputCreator,您将获得ClassCastException
。