2011-09-20 58 views
4

我想在Java中实现某种组件系统。使用Java通配符

有一个接口,称为表

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

是否有可能实现这样的事情在类型安全的方式没有未经检查的强制转换?我的通配符的用法是否正确?

回答

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,导致第一个aList<X1>类型,第二个aList<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类型系统来表达,但它是由你的代码逻辑保证的。您可以合法地禁止警告,因为您已经“检查”以确保演员必须在运行时工作。

0

看一看Composite Pattern。然后,如果您认为使用泛型对您的问题有用,请阅读一个很好的教程,例如this one