如何返回通用接口引用作为工厂方法的输出

时间:2014-02-03 12:50:30

标签: java generics

我有一个名为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;]

我该如何解决这个问题?

5 个答案:

答案 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