Question

I have a class that parses a stream of data. Each chunk of data is called a Box. There are many different kinds of Boxes. 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" )?

Was it helpful?

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:

  1. 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
  2. the visitor pattern, which has at least two drawbacks:
    1. 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
    2. you have to know all the possible kinds of Boxes in advance when defining your Visitor interface

OTHER TIPS

When you use wildcards, they may kind of "lose" their identity. Once a wildcard is used in an expression, it might produce a new type involving a wildcard, but that wildcard is not known to be the same as the original wildcard (you might know they're the same).

The problem in your case is that the type of clazz contains a wildcard, and clazz is used in two places, but by the time they meet up again, the compiler no longer knows they're the same type.

What you can do is write a capture helper, a private generic method with an explicit type parameter T which will prevent the wildcard's identity from being lost while inside this method. You can still pass a variable containing a wildcard into this method, because of capture.

private <T extends Box> void helperMethod(Class<T> clazz, Box box, Registry registry) {
    registry.get( clazz ).parse( clazz.cast( box ) );
}

// then you use it like in the place you had before:
for ( Box box : boxes ) {
    Class<? extends Box> clazz = box.getClass();
    helperMethod(clazz, box, registry);
}

On an unrelated note, the types of methods in your Registry class are not safe. For example, get returns type S which is not present in any of the arguments, so the caller of the method can request any type that extends Parser<T> as the result, and the method will return that type. How can that be safe? It should be written like this:

@SuppressWarnings( "unchecked" )
<T extends Box> Parser<T> get( Class<T> clazz ) {
    return (Parser<T>) unsafeMap.get( clazz );
}

Also, the add method is unnecessarily verbose. It can be simplified to (completely equivalent):

<T extends Box> void add( Class<T> clazz, Parser<T> parser ) {
    unsafeMap.put( clazz, parser );
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top