使用Java通配符

时间:2011-09-20 12:43:17

标签: java generics wildcard existential-type

我想用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;
    }
}

是否有可能以类型安全的方式实现这样的事情而不进行未经检查的强制转换?我对通配符的使用是否正确?

2 个答案:

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

答案 1 :(得分:0)

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