Java generics self-reference: is it safe?

  • A+
Category:Languages

I have this simple interface:

public interface Node<E extends Node<E>> {     public E getParent();      public List<E> getChildren();      default List<E> listNodes()     {         List<E> result = new ArrayList<>();          // ------> is this always safe? <-----         @SuppressWarnings("unchecked")         E root = (E) this;          Queue<E> queue = new ArrayDeque<>();         queue.add(root);          while(!queue.isEmpty())         {             E node = queue.remove();              result.add(node);              queue.addAll(node.getChildren());         }          return result;     } } 

I see that this is always an instance of Node<E> (by definition).
But I can't imagine a case where this is not an instance of E...
Since E extends Node<E>, shouldn't Node<E> also be equivalent to E by definition??

Can you give an example of an object that's an instance of Node<E>, but it's not an instance of E??

Meanwhile, my brain is melting...


The previous class was a simplified example.
To show why I need a self-bound, I'm adding a bit of complexity:

public interface Node<E extends Node<E, R>, R extends NodeRelation<E>> {     public List<R> getParents();      public List<R> getChildren();      default List<E> listDescendants()     {         List<E> result = new ArrayList<>();          @SuppressWarnings("unchecked")         E root = (E) this;          Queue<E> queue = new ArrayDeque<>();         queue.add(root);          while(!queue.isEmpty())         {             E node = queue.remove();              result.add(node);              node.getChildren()                 .stream()                 .map(NodeRelation::getChild)                 .forEach(queue::add);         }          return result;     } }  public interface NodeRelation<E> {     public E getParent();      public E getChild(); } 

 


An easy example to illustrate the problem: a node of a different type of node:

class NodeA implements Node<NodeA> {     ... } 

And:

class NodeB implements Node<NodeA> {     ... } 

In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: