How to implement container / element classes using type parameters?

My problem is in a short and fairly abstract form:

I would like to implement the Container class, which is parameterized by the type of the Element class for the elements contained in it (i.e. Container<T extends Element> ). Nothing special yet. But: the Element class and its subclasses must provide a register method that adds an Element instance to this Container class (ie register(Container<? super xxx> container) { ...} )

I think this problem should be accessible as follows. However, the following code is invalid. In particular, type parameters in ElementBasis#register and Sub1Element#register result in name collision errors. However, I think it should be possible to find the correct implementation of this problem.

 public interface Element { void register(Container<T super Element> container); } public class ElementBasis { @Override void register(Container<? super ElementBasis> container) { container.add(this); } } public class Sub1Element extends ElementBasis { // ... @Override void register(Container<? super Sub1Element> container) { container.add(this); } } public class Sub2Element extends ElementBasis { // ... } 

In addition, I would like to be able to give the elements a structure by providing a subclass of ElementGroup Element :

 public class ElementGroup<T extends Element> extends ElementBasis { // ... @Override void register(Container<? super T> container) { foreach(T member : groupMemebers) { container.add(member) } } } 

I also tried to solve the problem by parameterizing the Element classes so that its type parameter can be used in the register method. Unfortunately, without success.

Can anyone find the right implementation?

+1
java generics
source share
1 answer

Unlike some other languages. Java does not provide a keyword to mean "this class." So there is no easy way to ensure that register only a Container type ? super this class ? super this class . As a result, getting the string container.add(this); problematic for work.

A possible workaround is to create an Element generic, as described here . There are all kinds of problems with this approach (I personally don’t like it), including the problem solved by "getThis trick".

One of the problems is that it will not work with a chain of 3 classes / interfaces

 Sub1Element extends ElementBasis implements Element 

You can do this if you are doing ElementBasis generic as well as Element (all solutions to the related question include only chains of length 2). Here, I just replaced Collection with Container .

 public interface Element<E extends Element<E>> { void register(Collection<? super E> container); } public class ElementBasis<E extends ElementBasis<E>> implements Element<E> { @Override public void register(Collection<? super E> collection) { collection.add((E) this); // Unchecked cast } } public class Sub1Element<E extends Sub1Element<E>> extends ElementBasis<E> { @Override public void register(Collection<? super E> collection) { collection.add((E) this); // Unchecked cast } } 

This works, but since ElementBasis is a specific generic class with a self-referential constraint, you can only use it with wildcards.

This compiles cleanly:

 ElementBasis<?> e = new ElementBasis<>(); List<ElementBasis<?>> list = new ArrayList<>(Arrays.<ElementBasis<?>>asList(e, e)); e.register(list); System.out.println(list); 

However, wildcards are extremely confusing and seem unnecessary at first (although they are not).

Given all the problems with this approach, I would avoid this.

My preferred approach would be to get rid of all type parameters, stop trying to make register member of the Element and use the static method instead.

 public static <E extends Element> void register(E e, Collection<? super E> collection) { collection.add(e); } 
+2
source share

All Articles