我想用Java实现某种组件系统。
有一个名为Form
的界面interface Form<T> {
T getObject();
// ...
}
我想提供一些名为CompoundForm的抽象类来帮助从简单的表单构建复杂的表单。
CompoundForm的用户需要使用组件接口
提供每个组件的一些描述interface Component<T, U> {
/** Factory method to build new form for given component */
Form<U> createForm(U u, String prefix);
/** Extract component of type U from the compound t */
U get(T t);
/** Mutate t or build new compound of type T using information from u */
T set(T t, U u);
}
鉴于此接口,CompoundForm实现类似于:
abstract class CompoundForm<T> implements Form<T> {
/** User should override this method and provide a collection of
* actual components of different types, hence ? wildcard */
protected abstract Map<String, Component<T, ?>> componentMap();
private Map<String, Form<?>> formMap = new TreeMap<String, Form<?>>();
private final T object;
public CompoundForm(T object, String prefix) {
this.object = object;
for (Entry<String, Component<T, ?>> e: componentMap()) {
String subPrefix = e.getKey();
Component<T, ?> component = e.getValue();
// !!! Compile error here: type error
Form<?> form = component.createForm(component.get(object), prefix + subPrefix);
formMap.put(subPrefix, form);
}
}
public T getObject() {
T result = object;
for (Entry<String, Component<T, ?>> e: componentMap()) {
String subPrefix = e.getKey();
Component<T, ?> component = e.getValue();
Form<?> form = formMap.get(subPrefix);
// !!! Compile error here: type error
result = component.set(result, form.getObject());
}
return result;
}
}
是否有可能以类型安全的方式实现这样的事情而不进行未经检查的强制转换?我对通配符的使用是否正确?
答案 0 :(得分:3)
直观地说,您的代码非常有意义;但Java类型系统的限制使其成为非法。让我们先看一个更简单的例子
<T> void f1(List<T> a){ ... }
<T> void f2(List<T> a1, List<T> a2){ ... }
List<?> a = ...;
f1(a); // compiles
f2(a, a); // does not compile
编译f1(a)
时,编译器会在内部将a
的类型视为List<X>
,其中X是固定的,虽然是未知类型。这称为“通配符捕获”。将List<X>
传递给f1
编译,编译器推断出T = X。
编译f2(a,a)
时,会发生类似的事情;但是,通配符捕获分别应用于两次a
,导致第一个a
为List<X1>
类型,第二个a
为List<X2>
。编译器不会分析a
因此保持不变X1=X2
。没有这些知识,将List<X1>
和List<X2>
传递给f2()
不会编译。
解决方法是让a
只出现一次:
List<?> a = ...;
f2_1(a); // compiles
<T> void f2_1(List<T> a){ f2_2(a,a); } // compiles, two a's same type: List<T>
<T> void f2_2(List<T> a1, List<T> a2){ ... }
回到你的情况,你也需要一个辅助方法:
<T, U> Form<U> createForm(Component<T, U> component, T object, String prefix)
{
return component.createForm(component.get(object), prefix + subPrefix);
}
--
Component<T, ?> component = e.getValue();
Form<?> form = createForm(component, object, prefix + subPrefix);
对于下一个问题,你需要一个演员表。没有其他方法可以告诉编译器组件和表单共享相同的U
。该关系不能在Java类型系统中表达,但它由您的代码逻辑保证。您可以合法地禁止警告,因为您已经“检查”以确保演员必须在运行时工作。
答案 1 :(得分:0)
查看Composite Pattern。然后,如果您认为使用泛型对您的问题很有用,那就去阅读一个很好的教程,例如this one。