I have a generic interface Handler
public interface Handler<T> {
void handle(T obj);
}
I can have n implementations of this interface. Let's say I have following 2 implementations for now. One which handles String objects and another handles Date
public class StringHandler implements Handler<String> {
@Override
public void handle(String str) {
System.out.println(str);
}
}
public class DateHandler implements Handler<Date> {
@Override
public void handle(Date date) {
System.out.println(date);
}
}
I want to write a factory which will return handler instances based on the class type. Something like this :
class HandlerFactory {
public <T> Handler<T> getHandler(Class<T> clazz) {
if (clazz == String.class) return new StringHandler();
if (clazz == Date.class) return new DateHandler();
}
}
I get following error in this factory :
Type mismatch: cannot convert from StringHandler
to Handler<T>
How to fix this?
SIMPLE SOLUTION
You could save your mappings Class<T> -> Handler<T>
in a Map
. Something like:
Map<Class<T>, Handler<T>> registry = new HashMap<>();
public void registerHandler(Class<T> dataType, Class<? extends Handler> handlerType) {
registry.put(dataType, handlerType);
}
public <T> Handler<T> getHandler(Class<T> clazz) {
return registry.get(clazz).newInstance();
}
In some place, initialize handlers (could be in the factory itself):
factory.registerHandler(String.class, StringHandler.class);
factory.registerHandler(Date.class, DateHandler.class);
And in another place, you create and use them:
Handler<String> stringhandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);
MORE COMPLEX SOLUTION
You can "scan" classes using reflection and, instead of register manually the mappings Class<T> -> Handler<T>
, do it using reflection.
for (Class<? extends Handler> handlerType : getHandlerClasses()) {
Type[] implementedInterfaces = handlerType.getGenericInterfaces();
ParameterizedType eventHandlerInterface = (ParameterizedType) implementedInterfaces[0];
Type[] types = eventHandlerInterface.getActualTypeArguments();
Class dataType = (Class) types[0]; // <--String or Date, in your case
factory.registerHandler(dataType, handlerType);
}
Then, you create and use them like above:
Handler<String> stringhandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);
To implement getHandlerClasses()
, look at this to scan all classes in your jar
. For each class, you have to check if it is a Handler
:
if (Handler.class.isAssignableFrom(scanningClazz) //implements Handler
&& scanningClazz.getName() != Handler.class.getName()) //it is not Handler.class itself
{
//is a handler!
}
Hope it helps!
Your problem is that the compiler cannot make the leap to the fact thet the type of the result is correct.
To help the compiler you can make the factory delegate the construction. Although this looks strange and unwieldly it does manage to properly maintain type safety without sacrifices such as casting or using ?
or raw types.
public interface Handler<T> {
void handle(T obj);
}
public static class StringHandler implements Handler<String> {
@Override
public void handle(String str) {
System.out.println(str);
}
}
public static class DateHandler implements Handler<Date> {
@Override
public void handle(Date date) {
System.out.println(date);
}
}
static class HandlerFactory {
enum ValidHandler {
String {
@Override
Handler<String> make() {
return new StringHandler();
}
},
Date {
@Override
Handler<Date> make() {
return new DateHandler();
}
};
abstract <T> Handler<T> make();
}
public <T> Handler<T> getHandler(Class<T> clazz) {
if (clazz == String.class) {
return ValidHandler.String.make();
}
if (clazz == Date.class) {
return ValidHandler.Date.make();
}
return null;
}
}
public void test() {
HandlerFactory factory = new HandlerFactory();
Handler<String> stringHandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);
}
You can use something like:
class HandlerFactory {
public <T> Handler<T> getHandler(Class<T> clazz) {
if (clazz.equals(String.class)) return (Handler<T>) new StringHandler();
if (clazz.equals(Date.class)) return (Handler<T>) new DateHandler();
return null;
}
}
T
is generic and the compiler can't map that at compile time. Also it is safer to use .equals
instead of ==
.
The whole point of using a generic type is to share the implementation. If the n implementation of your Handler interface are so different that they can't be shared, then I don't think there is any reason to use define that generic interface at the first place. You'd rather just have StringHandler and DateHandler as top level classes.
On the other hand, if the implementation can be shared, as is the case of your example, then the factory works naturally:
public class Main {
static public interface Handler<T> {
void handle(T obj);
}
static public class PrintHandler<T> implements Handler<T> {
@Override
public void handle(T obj) {
System.out.println(obj);
}
}
static class HandlerFactory {
public static <T> Handler<T> getHandler() {
return new PrintHandler<T>();
}
}
public static void main(String[] args) {
Handler<String> stringHandler = HandlerFactory.getHandler();
Handler<Date> dateHandler = HandlerFactory.getHandler();
stringHandler.handle("TEST");
dateHandler.handle(new Date());
}
}
Basically you can do:
public Handler getHandler( Class clazz ){
if( clazz == String.class ) return new StringHandler();
if( clazz == Date.class ) return new DateHandler();
return null;
}
public static void main( String[] args ){
HandlerFactory handlerFactory = new HandlerFactory();
StringHandler handler = ( StringHandler )handlerFactory.getHandler( String.class );
handler.handle( "TEST" );
DateHandler handler2 = ( DateHandler )handlerFactory.getHandler( Date.class );
handler2.handle( new Date() );
}
Output:
TEST
Tue Dec 15 15:31:00 CET 2015
But instead writing two different methods to get handlers separately always is a better way.
I edited your code and allowed Eclipse to "fix" the errors and it came up with this.
public Handler<?> getHandler(Class<?> clazz) {
if (clazz == String.class)
return new StringHandler();
if (clazz == Date.class)
return new DateHandler();
return null;
}
Yout HandlerFactory don't know about T. Use your factory like below-
public class HandlerFactory {
public Handler<?> getHandler(Class<?> clazz) {
if (clazz == String.class) {
return new StringHandler();
}
if (clazz == Date.class) {
return new DateHandler();
}
return null;
}
}