Using Java Wildcards

I want to implement some component system in Java.

There is an interface called Form

interface Form<T> { T getObject(); // ... } 

And I would like to provide some abstract class called CompoundForm to help create complex forms from simple forms.

The CompoundForm user must provide some description of each component using the 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, the implementation of CompoundForm looks something like this:

 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 with a safe type without uncontrolled drops? Is my use of patterns correct?

+4
source share
2 answers

Intuitively, your code makes sense; however, a restriction in a system such as Java makes it illegal. First, consider a simpler example.

 <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 considers type a as List<X> , where X is a fixed, albeit unknown type. This is called a wildcard. By passing the compilation List<X> to f1 , the compiler reports that T = X.

When compiling f2(a,a) , this happens; however, group capture is applied to two occurrences of a separately, as a result, the first a is of type List<X1> , and the second a of type List<X2> . The compiler does not analyze that a remains unchanged, therefore X1=X2 . Without this knowledge, passing List<X1> and List<X2> to f2() will not compile.

The workaround is to do a only once:

 List<?> a = ...; f2_1(a); // compiles <T> void f2_1(List<T> a){ f2_2(a,a); } // compiles, two a same type: List<T> <T> void f2_2(List<T> a1, List<T> a2){ ... } 

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

 <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 will need a throw. Another way to tell the compiler is that the component and the form have the same U This relationship cannot be expressed in a system like Java, but it is guaranteed by your code logic. You can legitimately suppress a warning because you have "checked" to make sure that the cast should work at run time.

+3
source

See Composite Template . Then, if you think that using generics is good for your problem, go read a good tutorial like this one .

0
source

All Articles