我有一些带有泛型的代码片段,它被剥离到它的核心看起来像这样。有一些简单的通用接口
public interface Doubler<T> {
T doubling (T param);
}
并且有工厂方法返回此接口的不同实例
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;
}
}
}
除了工厂方法“produceDoubler”中的最后一行产生“未选中分配”警告外,一切正常。我实际上理解为什么 - 我无法弄清楚如何编写这个片段,以便Java编译器对分配完全满意。
答案 0 :(得分:3)
public static <T> Doubler<T> produceDoubler(String type) {
此签名所说的是,有人可以针对来电者想要的任何类型produceDoubler
致电T
,完全而不管{{1} },type
可以返回相应的Doubler。当然,它不能。但这可能会做一些愚蠢的事情
produceDoubler
当然没有意义。没有明智的方法来加倍颜色,并且类型和字符串之间没有关系。
使编译器满意的唯一方法是返回Doubler<Color> doubler = produceDoubler("Integer");
,但当你试图使用 {{1}时,这当然会迫使你做不安全的强制转换。 }。
实际上不是一种类型安全的方法 - 将任意Doubler<?>
连接到不同的类型。唯一类型安全的替代方案是提升Doubler
- 在顶层做类似
String
...无需在任何时候尝试存储非具体类型的倍增器。
答案 1 :(得分:0)
工厂方法的可能签名将要求所需类型的类:
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);
这样,您可以保持代码的编译时类型安全性,同时仍然可以证明工厂接口。
不同的实现可以使用Class<?>
键映射到Supplier<? extends Doubler<?>>
,允许您在运行时添加/删除映射。您将再次进行未经检查的演员表,但这次可以安全地进行演绎:
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;
}
}