Using Java wildcards
I want to 开发者_JAVA百科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?
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.
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.
精彩评论