Using Java wildcards

谁说胖子不能爱 提交于 2019-12-12 09:42:48

问题


I want to implement some kind of component system in Java.

There is an interface, called Form

interface Form<T> {
    T getObject();

    // ...
}

And I'd like to provide some abstract class called CompoundForm to assist building complex forms from simple forms.

User of CompoundForm needs to provide some description of each component using Component interface

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

Given this interface CompoundForm implementation is something like:

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

Is it possible to implement something like this in type-safe manner without unchecked casts? Is my usage of wildcards correct?


回答1:


Intuitively your code makes perfect sense; however a limitation in Java type system makes it illegal. Let's see a simpler example first

<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

When compiling f1(a), compiler internally treats the type of a as List<X>, where X is a fixed albeit unknown type. This is called "wildcard capture". Passing a List<X> to f1 compiles, compiler infers that T=X.

When compiling f2(a,a), similar thing happens; however, wildcard capture is applied on two occurrences of a seperately, resulting the 1st a being of type List<X1>, and the 2nd a of List<X2>. Compiler doesn't not analyze that a remain unchanged therefore X1=X2. Without that knowledge, passing List<X1> and List<X2> to f2() does not compile.

The workaround is to make a appear only once:

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){ ... }

Back to your case, you need a helper method too:

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

For the next problem, you need a cast. There is no other way to tell compiler that the component and the form share the same U. That relation cannot be expressed in Java type system, but it is guaranteed by your code logic. You can legitimately suppress the warning, because you have "checked" to make sure that the cast must work at runtime.




回答2:


Have a look at the Composite Pattern. Then, if you think using generics is useful for your problem, go and read a good tutorial such as this one.



来源:https://stackoverflow.com/questions/7485325/using-java-wildcards

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!