7

我有以下课程:

public interface IDataSource<T> {
  public List<T> getData(int numberOfEntries);
}

public class MyDataSource implements IDataSource<MyData> {
  public List<MyData> getData(int numberOfEntries) {
    ...
  }
}

public class MyOtherDataSource implements IDataSource<MyOtherData> {
  public List<MyOtherData> getData(int numberOfEntries) {
    ...
  }
}

我想使用一个根据数据类型返回正确实现的工厂。我写了以下内容,但收到“未经检查的演员表”警告:

public static <T> IDataSource<T> getDataSource(Class<T> dataType) {
    if (dataType.equals(MyData.class)) {
        return (IDataSource<T>) new MyDataSource();
    } else if (dataType.equals(MyOtherData.class)) {
        return (IDataSource<T>) new MyOtherDataSource();
    }

    return null;
}

我做错了吗?我该怎么做才能摆脱警告?

4

5 回答 5

5

我不知道有任何方法可以在没有@SuppressWarnings("unchecked").

您正在传递一个Class对象,因此T可以被捕获。但是您必须Class在运行时检查以确定IDataSource<T>要返回的内容。这个时候,类型擦除早就发生了。

在编译时,Java 不能确定类型安全。它不能保证at 运行时的返回值与返回的值相同T,因此会产生警告。ClassTIDataSource<T>

这看起来像是您被迫注释方法@SuppressWarnings("unchecked")以删除警告的情况之一。该警告的存在是有原因的,因此由您来提供和确保类型安全。如所写,看起来您已经提供了类型安全性。

@SuppressWarnings("unchecked")
public static <T> IDataSource<T> getDataSource(Class<T> dataType) {
于 2013-10-18T23:16:19.620 回答
4

你做得对,你应该简单地抑制警告。工厂是泛型中的棘手领域之一,您确实需要手动转换为泛型类型,并且您必须通过任何方式确保返回的值与Class<T>您传入的值匹配。例如,在这种情况下,您很难- 编码几个IDataSource实现,所以我建议编写单元测试来验证类型是否正确,这样如果MyData实现以不兼容的方式更改,您将在构建时收到错误。

只需用 注释该getDataSource方法@SuppressWarnings("unchecked"),在禁止警告时添加解释性注释总是一个好主意。

于 2013-10-18T23:18:16.413 回答
3

泛型用于编译时类型安全。它们不能用于这样的运行时类型确定。要消除警告,您可以执行类似的操作@SuppressWarnings("unchecked")或使用-Xlint:-unchecked编译器标志,如Java 教程的“原始类型”部分所述

于 2013-10-18T23:16:05.887 回答
3

其他答案已经回答了您提出的问题。但是我想退后一步来了解您要使用此工厂方法完成的工作。这个工厂基本上提供了数据类型到IDataSource参数的映射。依赖注入可能是更合适的模式,因为这是一组众所周知的数据类型和实现(如您的示例所示)。

假设您想将所有内容都存储Widgets在 Mongo 中,但所有内容都存储Gadgets在 Mysql 中,您可能有两个类:a MongoWidgetDataSourcethat implementsIDataSource<Widget>和 a MysqlGadgetDataSourcethat implements IDataSource<Gadget>

我不会像在数据消费者内部那样硬编码工厂方法调用MyFactory.getDataSource(Widget.class),而是注入适当的IDataSource依赖项。我们可能会对MyService小部件(存储在 mongo 中)做一些事情。按照您的建议使用工厂将如下所示:

public class MyService {
  public void doSomething() {
    String value = MyFactory.getDataSource(Widget.class).getSomething();
    // do something with data returned from the source
  }
}

相反,您应该将适当的数据源作为构造函数 arg 注入到服务中:

public class MyService {
  private final IDataSource<Widget> widgetDataSource;

  public MyService(IDataSource<Widget> widgetDataSource) {
    this.widgetDataSource = widgetDataSource;
  }

  public void doSomething() {
    String value = widgetDataSource.getSomething();
    // now do something with data returned from the source
  }
}

这具有使您的代码更可重用和更易于单元测试(模拟依赖项)的额外好处。

然后,在您实例化的地方MyService,您还可以连接您的数据源。许多项目使用依赖注入框架(如Guice)来使这更容易,但这不是一个严格的要求。不过,就我个人而言,我从来没有在没有任何实际规模或持续时间的项目上工作过。

如果您不使用 DI 框架,则只需在创建调用服务时实例化依赖项:

public static void main(String[] args) {
    IDataSource<Widget> widgetDataSource = new MongoWidgetDataSource();
    IDataSource<Gadget> gadgetDataSource = new MysqlGadgetDataSource();
    MyService service = new MyService(widgetDataSource, gadgetDataSource);
    service.doSomething();
}

在 Guice 中,您可以像这样连接这些数据源:

public class DataSourceModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(new TypeLiteral<IDataSource<Widget>>() {}).to(MongoWidgetDataSource.class);
    bind(new TypeLiteral<IDataSource<Gadget>>() {}).to(MysqlGadgetDataSource.class);
  }
}

依赖倒置是一种思考问题的不同方式,但它可以带来更加解耦、可重用和可测试的代码库。

于 2013-10-19T00:50:57.393 回答
1

这似乎有效:

public static <T> IDataSource<T> getDataSource(MyData dataType) {
  System.out.println("Make MyDataSource");
  return (IDataSource<T>) new MyDataSource();
}

public static <T> IDataSource<T> getDataSource(MyOtherData dataType) {
  System.out.println("Make MyOtherDataSource");
  return (IDataSource<T>) new MyOtherDataSource();
}

public void test() {
  IDataSource<MyData> myDataSource = getDataSource((MyData) null);
  IDataSource<MyOtherData> myOtherDataSource = getDataSource((MyOtherData) null);
}

您可能更喜欢创建空的原型而不是null像我一样进行投射,但我认为这是一种可行的技术。

于 2013-10-19T16:41:20.347 回答