2

It seem that a generic container cannot use a more specific class that the one of a function signature with generic container.

How can I have a container which use more specific class that the one of the function it is passed to? Is that possible ? Did I misunderstood generic in Java ?

Please, look at the code, as it may be easier to understand that the question.

    package com.demo;

    public class Entity {
        public String baseField;
    }

    public class ChildEntity extends Entity {
        public String extraField;
    }

    public class EntityContainer<T extends Entity> {
        public T instance;
    }

    public class EntityContainerWithMeta<T extends Entity> extends EntityContainer<T> {
        public String childContainerMetaData;
    }

    public class EntityConsumer<T extends Entity> {
        public void consum(EntityContainer<T> container){

        }
    }

    public class DemoGeneric {

        private class ChildChildEntity extends ChildEntity{
            public String extraExtraField;
        }

        // Objective :
        // - using generic
        // - with a data container that hold a more specific Klass ( Klass extends Entity)
        // - pass it to a less specific consumer ( that consum Entity not Klass)

        // real word use case:
        // Entity is the base class for all my Business Specific Object
        // Container are List<T extends Entity>
        // Consumer are ListAdapter for ListView (extends BaseAdapter) (from Android framework)

        public void demo() {

            // compile: (but then container cannot return ChildEntity with its getter)
            //EntityContainerWithMeta<Entity> demoContainer =
            //new EntityContainerWithMeta<Entity>();

            // don't compile:
            EntityContainerWithMeta<ChildEntity> demoContainer = new EntityContainerWithMeta<ChildEntity>();

            demoContainer.instance = new DemoGeneric.ChildChildEntity();

            EntityConsumer<Entity> consumer = new EntityConsumer<Entity>();

            // here is the problem:
            // required: EntityContainer<Entity>
            // found: EntityContainerWithMeta<ChildEntity>
            consumer.consum(demoContainer);

            // if
            // ChildEntity extends Entity
            // and
            // EntityContainerWithMeta extends EntityContainer
            // why is this not compiling ?
        }
    }

EDIT:

Thanks to the answers, I found this article: http://blog.informatech.cr/2013/03/15/covariance-and-contravariance-in-java/

That explain well the problem mentioned by @kocko and @MarkoTopolnik

Loda
  • 1,970
  • 2
  • 20
  • 40
  • This is not a quirk of Java Generics, it is a fundamental truth. `Container` simply *isn't* a `Container`, much like a `Hotel` isn't a `Hotel`. – Marko Topolnik Jan 28 '15 at 12:32
  • @MarkoTopolnik this work with array container, which make me think this is a mistake of mine or a limitation of Java Generic. eg: you can set "ChildEntity[] childEntityContainer" with "Entity[] heterogeneousContainer" – Loda Jan 28 '15 at 14:01
  • 1
    That's actually because Java's array type system is broken and leads to runtime type check failures. – Marko Topolnik Jan 28 '15 at 14:10

2 Answers2

1

Change your consumer declaration to:

EntityConsumer<? extends Entity> consumer = new EntityConsumer<ChildEntity>();

If ChildEntity extends Entity and EntityContainerWithMeta extends EntityContainer why is this not compiling ?

Because generics are not implicitly polimorphic.

Community
  • 1
  • 1
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • This does not compile neither: method consum in class EntityConsumer cannot be applied to given types; consumer.consum(demoContainer); ^ required: EntityContainer found: EntityContainerWithMeta reason: actual argument EntityContainerWithMeta cannot be converted to EntityContainer by method invocation conversion where T is a type-variable: T extends Entity declared in class EntityConsumer where CAP#1 is a fresh type-variable: CAP#1 extends Entity from capture of ? extends Entity – Loda Jan 28 '15 at 14:17
  • Note that, would it compile, I would loose my objective, which is to have a consumer that accept any Entity sub-class. – Loda Jan 28 '15 at 14:18
0

See if the following works, although I think it should:

public class EntityConsumer {
    public <T extends Entity, M extends EntityContainer<T>> void consum(M container){

    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 1st solution compile !! – Loda Jan 28 '15 at 14:31
  • Thanks for checking, I wasn't certain about the second. Is the first what you need? – EpicPandaForce Jan 28 '15 at 14:34
  • I think this is the closest you could get, but I "lost" the `new EntityConsumer****()` – Loda Jan 28 '15 at 15:00
  • But `EntityConsumer` will work for all subclasses of Entity, or at least you will be able to access them as `Entity`. – EpicPandaForce Jan 28 '15 at 15:01
  • sur, but I could not "enjoy" a simple getter in my `Consumer` that would return me the good Entity sub-class without casting, nor can I use field of Entity sub-class. (because `instanceof` does not work for generic) – Loda Jan 28 '15 at 15:06
  • technically `getClass().isAssignableFrom()` and `getClass().isInstance()` work, although that'd be ugly and kinda ruin the point of generics, unless you also implement a `Visitor` pattern. – EpicPandaForce Jan 28 '15 at 15:11
  • Glad to see that helped in the end :) – EpicPandaForce Feb 03 '15 at 11:47