Java generics map of Class<T> to a Parser<T>

generic-collections, generics, java, type-safety

Solution

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:

- classic OO decomposition (that is, add a `parseSelf` method to `Box`), which I'm gathering from the question is not going to work for you, and clutters the `Box` API

- the visitor pattern, which has at least two drawbacks:

- you have to add a visitor acceptor to all flavors of `Box`, which seems like a problem for the same reason as classic OO decomposition

- you have to know all the possible kinds of `Box`es in advance when defining your Visitor interface

Problem

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" )`?

Original source

Related problems