1

My problem in a short and rather abstract form:

I would like to implement a Container class which is type-parametrized by the Element class type of the elements it contains (i.e. Container<T extends Element>). Nothing really special so far. But: The Element class and its sub-classes shall provide a registermethod that adds the Element instance to a given Containerclass (i.e. register(Container<? super xxx> container) { ...} )

I think this problem should be approachable in the following kind of way. However, the following code is not valid. In particular, the type parameters in ElementBasis#register and Sub1Element#registerleads to name clash errors. Still I think it should be possible to find a proper implementation of that 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 {
  // ... 
}

Moreover, I would like to be able to give the elements a structure by providing an ElementGroup sub-class of 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 parametrizing the Elementclasses such that its type parameter can be used in the register method. Unfortunately with no success.

Can anyone find a proper implementation?

1 Answers1

2

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

One possible workaround is to make Element generic, as explained here. There are all sorts of problems with this approach (I personally dislike it strongly), including the issue that is solved by the "getThis trick".

One problem is that it doesn't work easily with a chain of 3 classes/interfaces

Sub1Element extends ElementBasis implements Element

You can do it if you make ElementBasis generic as well as Element (all the solutions to the linked question only involve chains of length 2). Here I have substituted Collection for Container for simplicity.

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 does work, but because ElementBasis is a concrete 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, the wildcards are extremely confusing, and seem at first to be unnecessary (though they are not).

Considering all the problems with this approach, I would avoid it.

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

public static <E extends Element> void register(E e, Collection<? super E> collection) {
    collection.add(e);
}
Community
  • 1
  • 1
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116