Yet another generics question: Needs unchecked conversion to conform to ...

Discussion in 'Java' started by Michel T., Dec 29, 2006.

  1. Michel T.

    Michel T. Guest

    I spent a lot of time massaging the code below to avoid a warning,
    without success.

    I pasted a sample class and inner classes that shows what I want to do.
    I wrote two versions of a factory method: one that does not generate
    any warnings, and the other generates one.
    The return type of the second method is defined like this:

    BeanFactory<Bean> createFactory2(String className){
    ...
    }

    The returned BeanFactory is also parametized, and this is where my
    problem lies. Can anyone suggest how to avoid the warning, besides
    adding a @suppresswarning annotation?

    public class Generics {

    public BeanFactory createFactory1(String beanClass)
    throws Exception{

    Class<?> bc = Class.forName(beanClass);

    Class<? extends BeanFactory> bfc =
    bc.asSubclass(BeanFactory.class);

    Constructor<? extends BeanFactory> cstr =
    bfc.getConstructor(new Class[]{});

    return cstr.newInstance(new Object[]{});
    }

    public BeanFactory<Bean> createFactory2(String beanClass)
    throws Exception{

    Class<?> bc = Class.forName(beanClass);

    Class<? extends BeanFactory> bfc =
    bc.asSubclass(BeanFactory.class);

    Constructor<? extends BeanFactory> cstr =
    bfc.getConstructor(new Class[]{});

    // The following line causes the warning:
    return cstr.newInstance(new Object[]{});
    }

    public class BeanFactory<T extends Bean>{
    }

    public class Bean{
    }
    }
     
    Michel T., Dec 29, 2006
    #1
    1. Advertising

  2. Michel T.

    Tom Hawtin Guest

    Re: Yet another generics question: Needs unchecked conversion toconform to ...

    Michel T. wrote:

    >
    > public BeanFactory<Bean> createFactory2(String beanClass)
    > throws Exception{
    >
    > Class<?> bc = Class.forName(beanClass);
    >
    > Class<? extends BeanFactory> bfc =
    > bc.asSubclass(BeanFactory.class);
    >
    > Constructor<? extends BeanFactory> cstr =
    > bfc.getConstructor(new Class[]{});
    >
    > // The following line causes the warning:
    > return cstr.newInstance(new Object[]{});
    > }
    >
    > public class BeanFactory<T extends Bean>{
    > }


    My first suggestion is not to use reflection.

    If not, does BeanFactory<? extends Bean> work for you? (There is no
    guarantee that the T in a subclass of BeanFactory is exactly Bean.)

    Alternatively, create a bean factory factory. (But stick with using a
    Constructor rather than evil Class.newInstance.)

    Tom Hawtin
     
    Tom Hawtin, Dec 30, 2006
    #2
    1. Advertising

  3. Michel T.

    Michel T. Guest

    > My first suggestion is not to use reflection.

    I have to use reflection. This example is a very-reduced sample that
    does not reflect the reality where this is required. I created this
    simple class to show the warning.

    > If not, does BeanFactory<? extends Bean> work for you? (There is no
    > guarantee that the T in a subclass of BeanFactory is exactly Bean.)


    I would not mind changing the return value of createFactory2 to

    BeanFactory<? extends Bean> createFactory2(String beanClass)

    but I get the same warning.


    Also, I tried the following but it does not compile (thus it must not
    make sense either):

    public BeanFactory<? extends Bean> createFactory2(String beanClass)
    throws Exception{

    Class<?> bc = Class.forName(beanClass);

    Class<? extends BeanFactory<? extends Bean>> bfc =
    bc.asSubclass(BeanFactory.class);

    Constructor<? extends BeanFactory<? extends Bean>> cstr =
    bfc.getConstructor(new Class[]{});

    return cstr.newInstance(new Object[]{});
    }


    >
    > Alternatively, create a bean factory factory. (But stick with using a
    > Constructor rather than evil Class.newInstance.)


    I must use reflection to instantiate the factory. Otherwise, I would
    not be posting that question ;)

    --
    Michel T.
     
    Michel T., Dec 30, 2006
    #3
  4. Michel T.

    Daniel Pitts Guest

    Michel T. wrote:
    > > My first suggestion is not to use reflection.

    >
    > I have to use reflection. This example is a very-reduced sample that
    > does not reflect the reality where this is required. I created this
    > simple class to show the warning.
    >
    > > If not, does BeanFactory<? extends Bean> work for you? (There is no
    > > guarantee that the T in a subclass of BeanFactory is exactly Bean.)

    >
    > I would not mind changing the return value of createFactory2 to
    >
    > BeanFactory<? extends Bean> createFactory2(String beanClass)
    >
    > but I get the same warning.
    >
    >
    > Also, I tried the following but it does not compile (thus it must not
    > make sense either):
    >
    > public BeanFactory<? extends Bean> createFactory2(String beanClass)
    > throws Exception{
    >
    > Class<?> bc = Class.forName(beanClass);
    >
    > Class<? extends BeanFactory<? extends Bean>> bfc =
    > bc.asSubclass(BeanFactory.class);
    >
    > Constructor<? extends BeanFactory<? extends Bean>> cstr =
    > bfc.getConstructor(new Class[]{});
    >
    > return cstr.newInstance(new Object[]{});
    > }
    >
    >
    > >
    > > Alternatively, create a bean factory factory. (But stick with using a
    > > Constructor rather than evil Class.newInstance.)

    >
    > I must use reflection to instantiate the factory. Otherwise, I would
    > not be posting that question ;)
    >
    > --
    > Michel T.


    *Why* do you need to use reflection? I've found that in most
    circumstances, you don't need to use it, even if you *think* you do.

    Moving forward, assuming that you do indeed need to do it this way...
    what is the exact warning you get? Can you post an sscce
    <http://physci.org/codes/sscce/> that allows us to reproduce the
    problem?

    Anyway, good luck.
     
    Daniel Pitts, Dec 30, 2006
    #4
  5. Michel T.

    Michel T. Guest

    Thanks for your replies,

    Daniel Pitts a écrit :
    > *Why* do you need to use reflection? I've found that in most
    > circumstances, you don't need to use it, even if you *think* you do.


    I'll explain in an upcoming separate message.

    > Moving forward, assuming that you do indeed need to do it this way...
    > what is the exact warning you get? Can you post an sscce
    > <http://physci.org/codes/sscce/> that allows us to reproduce the
    > problem?


    The exat warning is:

    Type safety: The expression of type capture-of ? extends
    Generics.BeanFactory needs unchecked conversion to conform to
    Generics.BeanFactory<? extends Generics.Bean>

    The "ssce" (thank you for the info on this acronym) was there in the
    first message: The "Generics" class compiles as is without any
    dependanies, and the warning shows up in Eclipse (I did not try on any
    other compiler). Here it is again with a slight modification in the
    return type of the createFactory2 method :

    public class Generics {

    public BeanFactory createFactory1(String beanClass) throws Exception{

    Class<?> bc = Class.forName(beanClass);
    Class<? extends BeanFactory> bfc =
    bc.asSubclass(BeanFactory.class);
    Constructor<? extends BeanFactory> cstr =
    bfc.getConstructor(new Class[]{});
    return cstr.newInstance(new Object[]{});
    }

    public BeanFactory<? extends Bean> createFactory2(String beanClass)
    throws Exception{

    Class<?> bc = Class.forName(beanClass);
    Class<? extends BeanFactory> bfc =
    bc.asSubclass(BeanFactory.class);
    Constructor<? extends BeanFactory> cstr =
    bfc.getConstructor(new Class[]{});
    // The following line causes a warning
    return cstr.newInstance(new Object[]{});
    }

    public class BeanFactory<T extends Bean>{
    }

    public class Bean{
    }
    }
     
    Michel T., Dec 30, 2006
    #5
  6. Michel T.

    Daniel Pitts Guest

    Michel T. wrote:
    > Thanks for your replies,
    >
    > Daniel Pitts a écrit :
    > > *Why* do you need to use reflection? I've found that in most
    > > circumstances, you don't need to use it, even if you *think* you do.

    >
    > I'll explain in an upcoming separate message.
    >
    > > Moving forward, assuming that you do indeed need to do it this way...
    > > what is the exact warning you get? Can you post an sscce
    > > <http://physci.org/codes/sscce/> that allows us to reproduce the
    > > problem?

    >
    > The exat warning is:
    >
    > Type safety: The expression of type capture-of ? extends
    > Generics.BeanFactory needs unchecked conversion to conform to
    > Generics.BeanFactory<? extends Generics.Bean>
    >
    > The "ssce" (thank you for the info on this acronym) was there in the
    > first message: The "Generics" class compiles as is without any
    > dependanies, and the warning shows up in Eclipse (I did not try on any
    > other compiler). Here it is again with a slight modification in the
    > return type of the createFactory2 method :
    >
    > public class Generics {
    >
    > public BeanFactory createFactory1(String beanClass) throws Exception{
    >
    > Class<?> bc = Class.forName(beanClass);
    > Class<? extends BeanFactory> bfc =
    > bc.asSubclass(BeanFactory.class);
    > Constructor<? extends BeanFactory> cstr =
    > bfc.getConstructor(new Class[]{});
    > return cstr.newInstance(new Object[]{});
    > }
    >
    > public BeanFactory<? extends Bean> createFactory2(String beanClass)
    > throws Exception{
    >
    > Class<?> bc = Class.forName(beanClass);
    > Class<? extends BeanFactory> bfc =
    > bc.asSubclass(BeanFactory.class);
    > Constructor<? extends BeanFactory> cstr =
    > bfc.getConstructor(new Class[]{});
    > // The following line causes a warning
    > return cstr.newInstance(new Object[]{});
    > }
    >
    > public class BeanFactory<T extends Bean>{
    > }
    >
    > public class Bean{
    > }
    > }


    Oh, okay.

    There isn't a way to remove all warnings in this case.
    Whenever you use reflection to create an object, erasure prevents
    anyone from knowing what generic type it has. You'll have to use a
    @SupressWarning, or just live with the warning.
     
    Daniel Pitts, Dec 30, 2006
    #6
  7. Michel T.

    Tom Hawtin Guest

    Re: Yet another generics question: Needs unchecked conversion toconform to ...

    Michel T. wrote:
    >> My first suggestion is not to use reflection.

    >
    > I have to use reflection. This example is a very-reduced sample that
    > does not reflect the reality where this is required. I created this
    > simple class to show the warning.
    >
    >> If not, does BeanFactory<? extends Bean> work for you? (There is no
    >> guarantee that the T in a subclass of BeanFactory is exactly Bean.)

    >
    > I would not mind changing the return value of createFactory2 to
    >
    > BeanFactory<? extends Bean> createFactory2(String beanClass)
    >
    > but I get the same warning.


    Rather strangely it requires an intermediate variable. I have no idea
    why. Captures are odd.

    import java.lang.reflect.*;

    public class Generics {
    public BeanFactory<?> createFactory(
    String beanClassName
    ) throws
    NullPointerException,
    LinkageError,
    ExceptionInInitializerError,
    ClassNotFoundException,
    ClassCastException,
    NoSuchMethodException,
    SecurityException,
    InstantiationException,
    IllegalAccessException,
    IllegalArgumentException,
    InvocationTargetException
    {
    Class<?> rawClass = Class.forName(beanClass);
    Class<? extends BeanFactory> clazz =
    rawClass.asSubclass(BeanFactory.class);
    Constructor<? extends BeanFactory> ctor =
    clazz.getConstructor(new Class[] { });
    BeanFactory erased = ctor.newInstance(new Object[] { });
    BeanFactory<?> reified = erased;
    return reified;
    }

    public class BeanFactory<T extends Bean>{
    }

    public class Bean{
    }
    }

    >> Alternatively, create a bean factory factory. (But stick with using a
    >> Constructor rather than evil Class.newInstance.)

    >
    > I must use reflection to instantiate the factory. Otherwise, I would
    > not be posting that question ;)


    Why must?

    What I mean was to create something like:

    class BeanFactoryFactory {
    public BeanFactory<?> createFactory();
    }

    And Generics.createFactory would become:

    public BeanFactory<?> createFactory(String beanClassName) {
    BeanFactoryFactory factoryFactory = ... beanClassName ...
    return factoryFactory.createFactory();
    }

    Tom Hawtin
     
    Tom Hawtin, Dec 30, 2006
    #7
  8. Michel T.

    Michel T. Guest

    > Rather strangely it requires an intermediate variable. I have no idea
    > why. Captures are odd.

    I tried and you are right, it got rid of the warning. But doing so, I
    had changed the signature of the method that broke the client code
    elsewhere. So I am back to the original version of the method that has
    this signature:

    BeanFactory<Bean> createFactory2(Strig name)

    > > I must use reflection to instantiate the factory. Otherwise, I would
    > > not be posting that question ;)

    >
    > Why must?


    Here is a more complete example that explains in the comments why I am
    doing all of this. If you enjoy generics enough to take a look at it
    and comment, I'll be more than impressed!

    import java.lang.reflect.Constructor;

    public class Generics {

    /**
    * this code will be used in either an OSGi container,
    * or in a normal java application.
    * So Service implementations are loaded either from
    * the OSGi bundle context (when in an OSGi container)
    * or from the current class loader using reflection.
    * The service finder interface allows to locate the
    * Service implementation using a different strategy depending
    * on the runtime context.
    * Only the reflection strategy is shown here.
    * Experimenting with it, I found that I would
    * not have to define generices in the interfaces and class
    * if I did not have this method:
    *
    * public void doSomethingWith(T component);
    *
    * in the Service interface. Wanting to allow a generic type
    * as a parameter appears to force me to put generics everywhere.
    */
    public static interface ServiceFinder<T extends ServiceComponent> {
    public Service<ServiceComponent> find(String id) throws Exception;
    }

    public static class ServiceFinderUsingReflection
    implements ServiceFinder<ServiceComponent> {

    public Service<ServiceComponent> find(String serviceId)
    throws Exception{

    // in real life, the service class name
    // is computed differently using
    // a pattern or xml config file
    String serviceClass = serviceId;

    Class<?> bc = Class.forName(serviceClass);
    Class<? extends Service> bfc = bc.asSubclass(Service.class);

    Constructor<? extends Service> cstr =
    bfc.getConstructor(new Class[]{});
    Service<?> ret = cstr.newInstance(new Object[]{});

    // This is the warning I'd like to get rid of
    return (Service<ServiceComponent>)ret;
    }
    }

    public static interface Service<T extends ServiceComponent>{

    public T createServiceComponent();

    /**
    * Using generics here allows implementation class to
    * override using their own types.
    */
    public void doSomethingWith(T component);
    }

    public static abstract class ServiceComponent{
    }


    // ------------------------------------
    // Sub-system external implementation
    // of Bean and BeanFactory

    public static class ServiceComponentX extends ServiceComponent {
    public void doIt(){
    }
    }

    public static class ServiceX implements Service<ServiceComponentX>{

    /**
    * Here, using generics, I can define methods that expect
    * a ServiceComponentX.
    */
    public void doSomethingWith(ServiceComponentX c) {
    c.doIt();
    }

    public ServiceComponentX createServiceComponent() {
    return new ServiceComponentX();
    }
    }
    // ------------------------------------

    public static void main(String[] args) throws Exception {


    // Use the reflection finder. We may also have chosen to
    // use, say, an OSGi-based finder that locates the service
    // using the OSGi bundle context.
    ServiceFinder<ServiceComponent> finder =
    new ServiceFinderUsingReflection();

    // Locate the service identified by the service id "ServiceX".
    // In this example, it happens to be the class name but in real
    // life, it is not the class name, but an ID from which the finder
    // implementation uses to locate the real service.
    Service<ServiceComponent> service = finder.find("ServiceX");

    ServiceComponent component = service.createServiceComponent();
    // Setup the component state here
    // ...

    // once it is setup,
    service.doSomethingWith(component);
    }
    }
     
    Michel T., Dec 31, 2006
    #8
  9. Michel T.

    Tom Hawtin Guest

    Re: Yet another generics question: Needs unchecked conversion toconform to ...

    Michel T. wrote:
    >> Rather strangely it requires an intermediate variable. I have no idea
    >> why. Captures are odd.

    > I tried and you are right, it got rid of the warning. But doing so, I
    > had changed the signature of the method that broke the client code
    > elsewhere. So I am back to the original version of the method that has
    > this signature:
    >
    > BeanFactory<Bean> createFactory2(Strig name)


    You have not and in general cannot verify that the object is of that
    type. The signature is wrong. For instance, in your example ServiceX is
    not a Service<ServiceComponent> but it is a Service<? extends
    ServiceComponent>.

    Generally a good move when using reflection and generics, is to add a
    layer of indirection, so you don't have to mix the two.

    So instead of loading Service<? extends ServiceComponent>, load a
    ServiceProvider:

    interface ServiceProvider {
    <T extends ServiceComponent> Service<T> find(
    Class<T> componentClass
    );
    }

    class ServiceProviderX implements ServiceProvider {
    @SuppressWarnings("unchecked")
    <T extends ServiceComponent> Service<T> find(
    Class<T> componentClass
    ) {
    if (componentClass != ServiceComponentX.class) {
    throw new SomeException();
    }
    return (Service<T>)new ServiceX();
    }
    }

    I think you are stuck with the unchecked warning there, but at least the
    code is now sound. Alternatively, add an asSubclass-like method to Service.

    Really, we want a Service interface for each type of ServiceComponent. So:

    public interface ServiceComponent {
    }
    public interface Service<T extends ServiceComponent> {
    T createServiceComponent();
    void doSomethingWith(T component);
    }
    public interface XServiceComponent implements ServiceComponent {
    }
    public interface XService extends Service<XServiceComponent> {
    }
    class XServiceComponentImpl implements XServiceComponent {
    }
    public class XServiceImpl implements XService {
    public XServiceComponent createServiceComponent() {
    return new XServiceComponentImpl();
    }
    public void doSomethingWith(XServiceComponent component) {
    ...
    }
    }
    ....
    public <T extends Service<?>> find(
    Class<T> clazz, String serviceClassName
    ) throws
    NullPointerException,
    LinkageError,
    ExceptionInInitializerError,
    ClassNotFoundException,
    ClassCastException,
    NoSuchMethodException,
    SecurityException,
    InstantiationException,
    IllegalAccessException,
    IllegalArgumentException,
    InvocationTargetException
    {
    ...
    return clazz.cast(service);
    }
    ....
    // Or: ServiceX service = find(
    Service<XServiceComponent> service = find(
    XService.class, className
    );

    Tom Hawtin
     
    Tom Hawtin, Dec 31, 2006
    #9
  10. Michel T.

    Michel T. Guest

    Tim, thank you very much for the feedback.

    > You have not and in general cannot verify that the object is of that
    > type. The signature is wrong. For instance, in your example ServiceX is
    > not a Service<ServiceComponent> but it is a Service<? extends
    > ServiceComponent>.


    But since ServiceComponent is an abstract class or interface, in
    practice, is'nt Service<? extends ServiceComponent> a "subset" of
    Service<ServiceComponent> ? Therefore, is'nt the cast from the later
    into the former safe?

    > Generally a good move when using reflection and generics, is to add a
    > layer of indirection, so you don't have to mix the two.


    On the other hand, it seems more work for the service implementation
    (ServiceX) who would have to provide two interfaces, and matching
    implementation classes. I may be missing a point though.

    I updated my test class to follow some of your recommandation. this is
    whet I end up with.

    import java.lang.reflect.Constructor;

    public class Generics2 {

    static interface ServiceProvider {
    <T extends ServiceComponent> Service<T> find(
    String className)
    throws Exception;
    }

    static class ServiceProviderWithReflection implements ServiceProvider
    {
    @SuppressWarnings("unchecked")
    public <T extends ServiceComponent> Service<T> find(
    String className) throws Exception {
    Class<?> bc = Class.forName(className);
    Class<? extends Service> bfc = bc.asSubclass(Service.class);

    Constructor<? extends Service> cstr =
    bfc.getConstructor(new Class[]{});
    Service<?> ret = cstr.newInstance(new Object[]{});

    // It seems we will have to live with this warning
    return (Service<T>)ret;
    }
    }

    static public interface ServiceComponent {
    }

    static public interface Service<T extends ServiceComponent> {
    T createServiceComponent();
    void doSomethingWith(T component);
    }

    static class ServiceComponentX implements ServiceComponent {
    }

    static public class ServiceX implements Service<ServiceComponentX> {
    public ServiceComponentX createServiceComponent() {
    return new ServiceComponentX();
    }
    public void doSomethingWith(ServiceComponentX component) {

    }
    }
    public static void main(String[] args) throws Exception {


    // Use the reflection finder. We may also have chosen to
    // use, say, an OSGi-based finder that locates the service
    // using the OSGi bundle context.
    ServiceProvider finder = new ServiceProviderWithReflection();

    // Locate the service identified by the service id "ServiceX".
    // In this example, it happens to be the class name but in real
    // life, it is not the class name, but an ID from which the finder
    // implementation uses to locate the real service.
    Service<ServiceComponent> service =
    finder.find(Generics2.ServiceX.class.getName());

    ServiceComponent component = service.createServiceComponent();
    // Setup the component state here
    // ...

    // once it is setup,
    service.doSomethingWith(component);
    }
    }


    --
    Michel T.
     
    Michel T., Dec 31, 2006
    #10
  11. Michel T.

    Tom Hawtin Guest

    Re: Yet another generics question: Needs unchecked conversion toconform to ...

    Michel T. wrote:
    > Tim, thank you very much for the feedback.
    >
    >> You have not and in general cannot verify that the object is of that
    >> type. The signature is wrong. For instance, in your example ServiceX is
    >> not a Service<ServiceComponent> but it is a Service<? extends
    >> ServiceComponent>.

    >
    > But since ServiceComponent is an abstract class or interface, in
    > practice, is'nt Service<? extends ServiceComponent> a "subset" of
    > Service<ServiceComponent> ? Therefore, is'nt the cast from the later
    > into the former safe?


    No. The set of all objects that are of type Service<? extends
    ServiceComponent> is a superset of all objects that are of type
    Service<ServiceComponent> (gloss over erasure here...).

    You had an interface:

    public interface Service<T extends ServiceComponent> {
    T createServiceComponent();
    void doSomethingWith(T component);
    }

    Suppose ServiceComponentX extends ServiceComponent. Then
    Service<ServiceComponentX> is a Service<? extends ServiceComponent>. Now
    if we could assign that to Service<ServiceComponent> we have a problem.


    Service<ServiceComponentX> a = new Service<ServiceComponentX>() {
    public ServiceComponentX createServiceComponent() {
    return new ServiceComponentX();
    }
    public void doSomethingWith(ServiceComponentX component) {
    // We hope this is true.
    System.err.println(component instanceof ServiceComponentX);
    }
    };
    Service<? extends ServiceComponent> b = a;
    Service<ServiceComponentX> c = b; // wrong

    // Here ServiceComponent is implicitly casted to ServiceComponentX!
    c.doSomething(new ServiceComponent());

    > On the other hand, it seems more work for the service implementation
    > (ServiceX) who would have to provide two interfaces, and matching
    > implementation classes. I may be missing a point though.


    Two interfaces containing no definitions.

    They are matching the types used by the client code. You could have more
    than one service per type (otherwise what would the point of dynamically
    loading them be?).

    Tom Hawtin
     
    Tom Hawtin, Dec 31, 2006
    #11
  12. Michel T.

    Michel T. Guest

    Tom, thanks a lot. Your comments and suggestions have been extremely
    useful to me.

    --
    Michel T.
     
    Michel T., Jan 2, 2007
    #12
  13. Re: Yet another generics question: Needs unchecked conversion toconform to ...

    Michel T. wrote:
    > But since ServiceComponent is an abstract class or interface, in
    > practice, is'nt Service<? extends ServiceComponent> a "subset" of
    > Service<ServiceComponent> ? Therefore, is'nt the cast from the later
    > into the former safe?


    This is a surprisingly common generics question (given it's answered
    darn near everywhere and every week here), and the answer, incidentally,
    is "no".

    Foo<X> and Foo<? extends X> are not subtypes of each other, because of
    example cases like this:

    List<Object> lo = new ArrayList<Object>();
    lo.add("foo"); // OK, a String is an Object.
    List<Integer> li = lo; // Fortunately won't work.
    Integer i = li.iterator().next(); // Uh-oh.

    List<Integer> li = new ArrayList<Integer>();
    List<Object> lo = li; // Fortunately won't work.
    lo.add("foo");
    Integer i = li.iterator().next(); // Uh-oh.

    Notice that either case allows us to wind up pulling a String from a
    List<Integer>! If we can get the same list referenced as a List<Object>
    and a List<Integer> we can put a non-Integer in via the former and pull
    out a nice fat ClassCastException via the latter. So much for
    compile-time type safety.

    Of course, a List<Integer> is a List<? extends Object>. And a
    List<Object> is a List<? super Integer>.

    The type safety rules basically don't let us put anything into a List<?
    extends Object> but we can read out Objects, and don't let us retrieve
    from a List<? super Integer> but we can put things in. Neither List
    sounds useful, but if we have a List<Integer> and pass it somewhere
    expecting a List<? extends Object> the latter can read our integers, and
    we can still put things in via the reference of type List<Integer>. The
    latter doesn't know they are integers, mind you, but it can count them,
    invoke all their toString methods, or whatever. Likewise if we pass a
    List<Object> to something expecting a List<? super Integer> that
    something can dump Integers into the list (and it can actually read
    Objects out, since whatever's in the list has to extend Object, but
    anything more requires an explicit cast).

    This comes in useful once you write generic stuff. Consider a game with
    a GameObject class. The instances are all things like game enemies,
    powerups, and even the player that can be drawn on the screen and can
    potentially move around and stuff. The GameObject class is polymorphic,
    and subclasses like Powerup don't do anything in their move() methods
    while others like Enemy use AI and Player uses the input device to
    decide where to move. (The input device might even be abstracted as a
    Connection, and you've got no problem later adding online multiplayer!
    Just add a NetworkConnection to the existing ConsoleConnection as
    concrete Connection types.) Now we want some generic display functions
    that display GameObjects from a List. One displays them in the main area
    at their proper positions; another displays them at particular
    coordinates in a grid in a corner area to show the graphics for
    currently active powerups.

    The first takes a List<GameObject> and is basically
    for (GameObject go : list) { go.display(go.getX(), go.getY() }

    The second takes a Set<Powerup>, since that's what our powerup state is,
    with objects added as they are acquired and removed when they run out.
    It does
    x = 0; y = 0; for (Powerup p : list) { p.display(x, y); /* some logic to
    move x and y*/ }

    Now we want to add that networked multiplayer and show the players'
    graphics in a scoreboard list from time to time. Why create a new
    function for this when players form a Set and we already have a
    Set<Powerup> displaying method? Let's change it to accept a Set<GameObject>.

    Oops. Ah yes, that's why we had it a Set<Powerup> to begin with; we
    can't assign our Powerup set to a Set<GameObject>. Or our Set<Player>
    either for that matter.

    But it's perfectly safe; the loop doesn't add anything to the Set after
    all! That's why we want:

    public void drawInGridAt
    (int x, int y, int size, Set<? extends GameObject> objects) {
    // some logic setting i and j up
    for (GameObject go : objects) {
    go.display(x + size*i, y + size*j);
    // some logic changing i and j
    }
    }

    Presto: we can pass a Set<Powerup> or a Set<Player> as a Set<? extends
    GameObject> and we can pull a GameObject from a Set<? extends
    GameObject>, which is all this method needs to do.


    Of course, there's a nice example for <? super Foo> as well. It's when
    we are inputting objects of a generic type but not retrieving them.
    Consider a pipeline of some kind in which objects are put into a
    Pipeline object and Pipelines notify their Listeners with these objects,
    allowing loose coupling and runtime rearrangement of the plumbing. This
    might be for process coordination or whatever. Ultimately, we need a
    Pipeline<T> to accept Ts and pass them to Listener<T>s. We also want to
    be able to join pipelines together so that many run together; if we do a
    pipelineA.mergeInto(pipelineB) we want any object sent to pipeline A to
    get sent to B as well, and on to anything B does in turn. We come up with:

    public interface Listener<T> {
    public void handleObject(T object);
    }

    public class Pipeline<T> {
    private List<Listener<T>> listeners
    = new List<Listener<T>>();

    public void handleObject(T object) {
    for (Listener<T> listener : listeners) {
    listener.handleObject(object);
    }
    }

    public void addListener (Listener<T> listener) {
    listeners.add(listener);
    }

    public void mergeInto (Pipeline<T> p) {
    // hmm ... how to do this?
    }
    }

    Then the brilliant idea strikes: just add "implements Listener<T>" to
    the class definition. Pipeline already has a handleObject(T) method, and
    that method does exactly what's needed to pass the object on to its own
    listeners, so adding a Pipeline as a listener to another Pipeline works
    beautifully. And mergeInto just has to behave like addListener(p)! In
    fact, the method could go away entirely, save that it helps callers
    write self-documenting code that makes it clear that major plumbing is
    merging and not just being tapped at that point.

    Unfortunately, we run into a problem when we go to merge a
    pipeline<Integer> to a pipeline<Object>. Logically we should be able to,
    since any listener that can cope with an Object can cope with an
    Integer, and a flow of Integers dumping into a flow of Objects isn't
    violating any type constraints, surely?

    It's "super" to the rescue:

    public class Pipeline<T> implements Listener<T> {
    private List<Listener<? super T>> listeners
    = new List<Listener<? super T>>();

    public void handleObject(T object) {
    for (Listener<? super T> listener : listeners) {
    listener.handleObject(object);
    // Fine -- object is a T so it can safely
    // be cast to any ? super T
    }
    }

    public void addListener (Listener<? super T> listener) {
    listeners.add(listener);
    }

    public void mergeInto (Pipeline<? super T> p) {
    listeners.add(p);
    }
    }

    This also lets us register a generic Object listener with an Integer
    pipeline, as well as merging pipelines of Integers, Strings, and
    whatever else into a pipeline of Objects.
     
    John Ersatznom, Jan 4, 2007
    #13
  14. Michel T.

    Michel T. Guest

    This is a very useful reply to my question. Thank you very much for
    taling the time to write it.

    --
    Michel T.
     
    Michel T., Jan 16, 2007
    #14
  15. Re: Yet another generics question: Needs unchecked conversion toconform to ...

    Michel T. wrote:
    > This is a very useful reply to my question. Thank you very much for
    > taling the time to write it.


    What is? You don't quote it and it doesn't seem to exist on my
    newsserver any more.
     
    John Ersatznom, Jan 18, 2007
    #15
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Steven Buroff
    Replies:
    5
    Views:
    5,562
    Alan Moore
    Sep 28, 2004
  2. Chris Uppal

    And yet another generics question

    Chris Uppal, Nov 2, 2005, in forum: Java
    Replies:
    11
    Views:
    536
    Chris Uppal
    Nov 4, 2005
  3. Replies:
    3
    Views:
    8,968
  4. Ragnar
    Replies:
    9
    Views:
    1,547
    Andy Dingley
    Oct 27, 2006
  5. RVic
    Replies:
    19
    Views:
    1,451
Loading...

Share This Page