CrazyDoggg
CrazyDoggg

Reputation: 463

Factory and generics

I have the following classes:

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) {
    ...
  }
}

I would like to use a factory that return the correct implementation based on the data type. I wrote the following but I get "Unchecked cast" warnings:

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;
}

Am I doing it wrong? What can I do to get rid of the warnings?

Upvotes: 7

Views: 234

Answers (5)

OldCurmudgeon
OldCurmudgeon

Reputation: 65811

This seems to work:

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);
}

You may prefer to create empty archetypes rather than cast null like I have but I think this is a viable technique.

Upvotes: 1

Cody A. Ray
Cody A. Ray

Reputation: 6017

The other answers have answered the problem as you posed it. But I'd like to take a step back to understand what you're trying to accomplish with this factory method. This factory basically provides a map of data types to IDataSource parameters. Dependency injection might be a more appropriate pattern since this is a small well-known set of data types and implementations (as indicated by your example).

Let's say you want to store all Widgets in Mongo but all Gadgets in Mysql, you might have two classes: a MongoWidgetDataSource that implements IDataSource<Widget> and a MysqlGadgetDataSource that implements IDataSource<Gadget>.

Instead of hardcoding a factory method call like MyFactory.getDataSource(Widget.class) inside a data consumer, I would inject the appropriate IDataSource dependency. We might have MyService that does something with widgets (stored in mongo). Using a factory as you proposed would look like this:

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

Instead, you should inject the appropriate data source as a constructor arg into the service:

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
  }
}

This has the added benefit of making your code more reusable and easier to unit test (mock dependencies).

Then, where you instantiate MyService, you can also wire up your data sources. Many projects use a dependency injection framework (like Guice) to make this easier, but its not a strict requirement. Personally, though, I never work on a project of any real size or duration without one.

If you don't use an DI framework, you just instantiate the dependencies when you create the calling service:

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

In Guice, you would wire up these data sources like this:

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);
  }
}

Dependency inversion is a bit of a different way to think about the problem, but it can lead to a much more decoupled, reusable and testable code base.

Upvotes: 3

You're doing it right, and you should simply suppress the warnings. Factories are one of the tricky areas in generics where you really do need to manually cast to a generic type, and you have to ensure via whatever means that the returned value matches the Class<T> you pass in. For example, in this case you're hard-coding a couple of IDataSource implementations, so I would recommend writing unit tests that verify that the types are correct so that if the MyData implementation changes in an incompatible way, you'll get an error on build.

Just annotate the getDataSource method with @SuppressWarnings("unchecked"), and it's always a good idea to add an explanatory comment when suppressing warnings.

Upvotes: 4

rgettman
rgettman

Reputation: 178263

I am not aware of any way to get rid of those warnings without @SuppressWarnings("unchecked").

You are passing in a Class object so T can be captured. But you are forced to check the Class at runtime to determine which IDataSource<T> to return. At this time, type erasure has long since occurred.

At compile time, Java can't be sure of type safety. It can't guarantee that the T in the Class at runtime would be the same T in the IDataSource<T> returned, so it produces the warning.

This looks like one of those times when you're forced to annotate the method with @SuppressWarnings("unchecked") to remove the warning. That warning is there for a reason, so it is up to you to provide and ensure type safety. As written, it looks like you have provided type safety.

@SuppressWarnings("unchecked")
public static <T> IDataSource<T> getDataSource(Class<T> dataType) {

Upvotes: 5

Ryan Stewart
Ryan Stewart

Reputation: 128809

Generics are for compile-time type safety. They can't be used for runtime type determination like that. To get rid of the warning, you can do something like @SuppressWarnings("unchecked") or use the -Xlint:-unchecked compiler flag, as described in the "Raw Types" part of the Java tutorial.

Upvotes: 3

Related Questions