1

I have java Generics related question

I have Parents class called Container like below: Container can contain other containers and Services. Services class extends Container but can contain only ServiceMembers which also extend Container.

The below code doesnt work even though in the Services class I override the parents class's getElements() method and return an ArrayList of elements which are also Subclass of the Container class.

Any idea on how I can get this to work or I am just using it the wrong way ?

public class Container
{
   protected ArrayList<Container> elements = new ArrayList<Container>();

   public ArrayList<Container> getElements()
   {
   }
}

public class Service extends Container
{
  public ArrayList<ServiceMember> getElements()
  {
  }
}
Ravi K Thapliyal
  • 51,095
  • 9
  • 76
  • 89
Chandu
  • 1,049
  • 3
  • 11
  • 18

3 Answers3

1

The below code doesnt work even though in the Services class I override the parents class's getElements() method and return an ArrayList of elements which are also Subclass of the Container class.

This is not correct. Due to type erasure ArrayList<ServiceMember> is not consider a co-variant return required for overriding getElements() method.

Override as follows. You don't lose out on Polymorphism and it would still happen since it depends on the Object's type (dynamic; at runtime) and not the type of its reference (static; at compile time).

public class Service extends Container
{
  public ArrayList<Container> getElements()
  {
    // declare superclass Container
    ArrayList<Container> elements = new ArrayList<Container>();

    // but add subclass ServiceMember
    elements.add(new ServiceMember());

    return elements;
  }
}
Ravi K Thapliyal
  • 51,095
  • 9
  • 76
  • 89
  • Thanks for your response. I did check other explanations on stack and understood the issue. But I tried a few solutions as well from other posts like but I am not very familiar with Generic so went ahead and asked the question to see if someone can help me in the right way to find a solution to what I am doing.. is there a better way to do this protected ArrayList extends Container> elements = new ArrayList(); – Chandu May 20 '13 at 01:52
1

I think you simply need to modify the Container class like this:

public class Container<T extends Container> {
   protected List<T> elements = new ArrayList<T>();

   public List<T> getElements() {
       return elements;  
   }
}

This will allow you to parametrize it with all of the subclasses of the Container class (or itself).

The Service class would look like this:

public class Service extends Container<Service> {

}

At final, you can instantiate objects of these classes like this:

Service s = new Service();
ArrayList<Service> elements = s.getElements();

Container<Container> c = new Container<Container>();
ArrayList<Container> els = c.getElements();

//adding an element to each of the lists:
c.getElements().add(new Container<Container>());
System.out.println(c.getElements().size());

s.getElements().add(new Service());
System.out.println(s.getElements().size());

Anyway, I would recommend you to think very carefully on the design, because mixing raw classes with generics is unsafe and confusing.

Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • Note that `Container` is a raw type in `T extends Container`. – Paul Bellora May 20 '13 at 01:47
  • Hi thanks for your response while I try the suggested solution.. was wondering if u were seeing a fundmental flaw in the way my objects are arranged.. can u advise .. thanks – Chandu May 20 '13 at 01:56
  • also how to I add to the Elements list ? I tried your suggestions and I am able to return the type of list but i see issue adding elements – Chandu May 20 '13 at 02:04
  • -1 Sorry, but mixing raw types with generics is confusing and potentially unsafe. – Paul Bellora May 20 '13 at 02:18
  • Ofcourse it is and that's why I recommended Chandu to consider very carefully his design snd the archtecture of classes. – Konstantin Yovkov May 20 '13 at 02:23
  • +1 `extends Container` won't work. Finally, a use case for `@SuppressWarnings("rawtypes")` :) – Ravi K Thapliyal May 20 '13 at 03:56
  • Thanks Everyone for helping me out and get a little more understanding of the generics. What Kocko suggested worked for me... and also for what Ravi suggested as an improvement for RAW – Chandu May 20 '13 at 03:59
  • 1
    @Chandu I strongly recommend against using this solution. Its pitfalls are subtle and may only become apparent later on. See [What is a raw type and why shouldn't we use it?](http://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it). Kocko is recommending the "curiously recurring template pattern", but his solution "cheats" with raw types. Even when implemented correctly, this pattern is open to abuse and mistakes. See my answer [here](http://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable/7355094#7355094). – Paul Bellora May 20 '13 at 04:06
  • 2
    @Chandu As kocko mentioned, the true solution is to rethink your design here. I sense that some of these classes are playing multiple roles and it will help to try and decouple each responsibility into its own interface/class. Also, look at using composition instead of inheritance where possible, since inheritance is playing a role in your issue here. See [Difference between Inheritance and Composition](http://stackoverflow.com/questions/2399544/difference-between-inheritance-and-composition) for more info on that concept. – Paul Bellora May 20 '13 at 04:13
-1

You need to type Container, to indicate the type of things being contained:

public class Container<T extends Container<T>> {
    private List<T> elements = new ArrayList<T>();

    public List<T> getElements() {
        return elements;
    }
}

public class Service extends Container<Service> {
}

Notes:

  • the type T extends Container<T> seems extend itself, but this is how you specify it such that you avoid the raw Container type
  • the list is private: subclasses should simply access it via the getter
  • the abstract type List is used instead of the concrete implementation ArrayList, in line with good practice
Bohemian
  • 412,405
  • 93
  • 575
  • 722