Reputation: 31
I have snippet of code with generics which is stripped to its core looks something like this. There is trivial generic interface
public interface Doubler<T> {
T doubling (T param);
}
and there is factory method that returns different instances of this interface
public class DoublerFactory {
public static void main(String[] args) {
Doubler<String> input = produceDoubler("String");
System.out.println(input.doubling(args[0]));
}
public static <T> Doubler<T> produceDoubler(String type) {
Doubler result;
switch (type) {
case "Integer" : result = new IntDoubler(); break;
case "Float" : result = new FloatDoubler(); break;
case "String" : result = new StringDoubler(); break;
default: result = null;
}
return result;
}
static class IntDoubler implements Doubler<Integer> {
@Override
public Integer doubling(Integer param) {
return param*2;
}
}
static class FloatDoubler implements Doubler<Float> {
@Override
public Float doubling(Float param) {
return param*2;
}
}
static class StringDoubler implements Doubler<String> {
@Override
public String doubling(String param) {
return param + param;
}
}
}
Everything works fine except that last line in factory method "produceDoubler" produces "Unchecked assignment" warning. I actually understand why - what I can't figure out is how to write this snippet so that Java compiler is fully satisfied with assignments.
Upvotes: 2
Views: 96
Reputation: 2605
A possible signature for your factory method would ask for the Class of the desired type:
public class DoublerFactory() {
public <T> Doubler<T> produceDoubler(Class<? extends T> cls) {
// This accepts both Class objects designating the primitive type and its object
// wrapper, although obviously the result always uses the Object-derived wrapper
if (Integer.TYPE.equals(cls) || Integer.class.equals(cls))
return new IntDoubler();
if (Float.TYPE.equals(cls) || Float.class.equals(cls))
return new FloatDoubler();
if (String.class.equals(cls))
return new StringDoubler();
throw new IllegalArgumentException();
}
}
// User code
DoublerFactory df = //...
Doubler<Float> flt = df.produceDoubler(Float.class);
Doubler<String> str = df.produceDoubler(String.class);
This way, you can maintain the compile-time type safety of the code, while still proving a factory interface.
A differnt implementation could use a map of Class<?>
keys to Supplier<? extends Doubler<?>>
, allowing you to add/remove mappings at runtime. You would once again have an unchecked cast, but this time it can be safely suppressed:
public class DoublerFactory() {
private Map<Class<?>, Supplier<? extends Doubler<?>> doublerMakers;
public DoublerFactory() {
doublerMakers = new HashMap<>();
addReg(Integer.TYPE, IntDoubler::new);
addReg(Integer.class, IntDoubler::new);
addReg(Float.TYPE, FloatDoubler::new);
addReg(Float.class, FloatDoubler::new);
addReg(String.class, StringDoubler::new);
}
// Using this function instead of directly calling Map.put in the constructor
// allows the compiler to check that the concordancy in T is respected
private <T> void addReg(Class<? extends T> cls, Supplier<? extends Doubler<T>> maker) {
doublerMakers.put(cls, maker);
}
public <T> Doubler<T> produceDoubler(Class<? extends T> cls) {
Supplier<? extends Doubler<?>> maker = doublerMakers.get(cls);
if (maker == null) throw new IllegalArgumentException();
// The cast here is unavoidable, but we know that if all mappings are inserted
// using the addReg() method, then the result is type safe.
@SuppressWarnings("unchecked")
Doubler<T> ret = (Doubler<T>)(Doubler)maker.get();
return ret;
}
}
Upvotes: 0
Reputation: 198103
public static <T> Doubler<T> produceDoubler(String type) {
What this signature says is that someone can call produceDoubler
for any type T
the caller wants, completely irrespective of type
, and produceDoubler
can return the appropriate Doubler. Of course, it can't. But that could do something silly like
Doubler<Color> doubler = produceDoubler("Integer");
which of course makes no sense. There's no sensical way to double a color, and there's no relationship between the type and the string.
The only way to make the compiler happy would be to return a Doubler<?>
, but this would of course force you to do unsafe casts when you tried to use the Doubler
.
There isn't actually a typesafe way to do this -- to connect arbitrary String
s to different types. The only typesafe alternative would be to lift out the switch
-- to do at the top level something like
switch (type) {
case "Double": {
Doubler<Double> doubler = new DoubleDoubler();
// use the doubler for everything you need to use it for
}
...
}
...without trying to store a doubler of a non-concrete type at any time.
Upvotes: 3