I have a class that parses a stream of data. Each chunk of data is called a Box
. There are many different kinds of Box
es. I want to have a different Parser
for each type of box. So basically I need a Registry
or something like one that will let me pull out the right parser for each Box
. Here is a simplified version of my problem:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GenericsTest {
class Box {
private String data;
public String getData() {
return data;
}
}
class BoxA extends Box {
private String adata;
BoxA( String adata ) {
this.adata = adata;
}
public String getAData() {
return adata;
}
}
class BoxB extends Box {
private String bdata;
BoxB( String bdata ) {
this.bdata = bdata;
}
public String getBData() {
return bdata;
}
}
interface Parser<T> {
public void parse( T box );
}
class ParserA implements Parser<BoxA> {
@Override
public void parse( BoxA box ) {
System.out.print( "BoxA: " + box.getAData() );
}
}
class ParserB implements Parser<BoxB> {
@Override
public void parse( BoxB box ) {
System.out.print( "BoxB: " + box.getBData() );
}
}
class Registry {
Map<Class<?>, Parser<?>> unsafeMap = new HashMap<>();
<T extends Box, S extends Parser<T>> void add( Class<T> clazz, S parser ) {
unsafeMap.put( clazz, parser );
}
<T extends Box> boolean containsKey( Class<T> clazz ) {
return unsafeMap.containsKey( clazz );
}
@SuppressWarnings( "unchecked" )
<T extends Box, S extends Parser<T>> S get( Class<T> clazz ) {
return (S) unsafeMap.get( clazz );
}
}
public void runTest() {
Registry registry = new Registry();
registry.add( BoxA.class, new ParserA() );
registry.add( BoxB.class, new ParserB() );
List<Box> boxes = new ArrayList<>();
boxes.add( new BoxA( "Silly" ) );
boxes.add( new BoxB( "Funny" ) );
boxes.add( new BoxB( "Foo" ) );
boxes.add( new BoxA( "Bar" ) );
for ( Box box : boxes ) {
Class<? extends Box> clazz = box.getClass();
registry.get( clazz ).parse( clazz.cast( box ) );
}
}
public static void main( String[] args ) {
new GenericsTest().runTest();
}
}
If you take that code and try to compile it, you see this error:
The method parse(capture#4-of ? extends GenericsTest.Box) in the type GenericsTest.Parser is not applicable for the arguments (capture#5-of ? extends GenericsTest.Box)
So the question is, how does
(capture#4-of ? extends GenericsTest.Box)
differ from
(capture#5-of ? extends GenericsTest.Box)
?
And, is there a better way than my Registry
approach that would not require the use of the @SuppressWarnings( "unchecked" )
?
First, let's answer the OP's question. What is the difference between (capture#4-of ? extends GenericsTest.Box)
and (capture#5-of ? extends GenericsTest.Box)
?
The compiler figures out that the class object passed to registry.get()
has type Class<x>
for some unknown x
extending Box
. Type inference thus instantiates the type T
of get()
with x
, and concludes that the parser it returns has type Parser<x>
for that same x
extending Box
. (It is unfortunate that the compiler uses terminology like "capture#4-of ?" to mean "for some x4 such that x4".) So far, so good.
What happens in general is that anytime you have two separate expressions (even syntactically identical ones) whose type is inferred to be of wildcard type, the existential variables are captured independently. You can "unify" these variables if the expressions occur in a non-wildcard context, usually a separate generic method.
Check this out:
public class WildcardTest {
private < T > void two( Class< T > t1, Class< T > t2 ) {}
private < T > void one( Class< T > t1 ) {
two( t1, t1 ); // compiles; no wildcards involved
}
private void blah() {
two( WildcardTest.class, WildcardTest.class ); // compiles
one( WildcardTest.class ); // compiles
Class< ? extends WildcardTest > wc = this.getClass();
two( wc, wc ); // won't compile! (capture#2 and capture#3)
one( wc ); // compiles
}
}
And this:
public class WildcardTest {
interface Thing< T > {
void consume( T t );
}
private < T > Thing< T > make( Class< T > c ) {
return new Thing< T >() {
@Override public void consume(T t) {}
};
}
private < T > void makeAndConsume( Object t, Class< T > c ) {
make( c ).consume( c.cast( t ) );
}
private void blah() {
Class< ? extends WildcardTest > wc = this.getClass();
make( wc ).consume( wc.cast( this ) ); // won't compile! (capture#2 and capture#3)
makeAndConsume( this, wc ); // compiles
}
}
The second example is the relevant one here. The following transformation gets rid of all the warnings except the one you already suppressed in Registry:
private < T extends Box > void getParserAndParse(
Registry registry, Class< T > clazz, Object box
) {
registry.get( clazz ).parse( clazz.cast( box ) );
}
public void runTest() {
Registry registry = new Registry();
registry.add( BoxA.class, new ParserA() );
registry.add( BoxB.class, new ParserB() );
List<Box> boxes = new ArrayList< Box >();
boxes.add( new BoxA( "Silly" ) );
boxes.add( new BoxB( "Funny" ) );
boxes.add( new BoxB( "Foo" ) );
boxes.add( new BoxA( "Bar" ) );
for ( Box box : boxes ) {
Class< ? extends Box > clazz = box.getClass();
getParserAndParse( registry, clazz, box ); // compiles
}
}
As for your second question, you are attempting to perform ad-hoc polymorphism via what amounts to a variant type (Box). There are two ways to achieve such a thing without type warnings:
parseSelf
method to Box
), which I'm gathering from the question is not going to work for you, and clutters the Box
APIBox
, which seems like a problem for the same reason as classic OO decompositionBox
es in advance when defining your Visitor interface