tipos que programación operaciones nodo metodo genérica genericos generico estatico datos con clase abstractos java generics this self-reference

que - Auto-referencia de los genéricos de Java: ¿es seguro?



que es generico en java (4)

Con este tipo de situación, a menudo es útil tener un método getThis que (por convención) devuelve this .

Yo haria lo siguiente

public interface Node<E extends Node<E, R>, R extends NodeRelation<E, R>> { public List<R> getParents(); public List<R> getChildren(); public List<E> listDescendants() ; } public interface NodeRelation<E extends Node<E, R>, R extends NodeRelation<E, R>> { public E getParent(); public E getChild(); } abstract class ANode<E extends ANode<E,R>, R extends ARelation<E,R>> implements Node<E,R> { abstract protected E getThis() ; public List<E> listDescendants() { List<E> result = new ArrayList<>(); E root = getThis() ; ... return result; } } abstract class ARelation<E extends ANode<E,R>, R extends ARelation<E,R>> implements NodeRelation<E,R> { } class CNode extends ANode<CNode, CRelation> { public CNode getThis() { return this ; } ... } class CRelation extends ARelation<CNode, CRelation> { ... }

Aunque puede que no me moleste en tener capas tanto de clase abstracta como de interfaz.

Tengo esta interfaz simple:

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; } }

Veo que this es siempre una instancia del Node<E> (por definición).
Pero no puedo imaginar un caso donde this no sea un ejemplo de E ...
Dado que E extends Node<E> , ¿no debería Node<E> también ser equivalente a E por definición?

¿Puede dar un ejemplo de un objeto que sea una instancia de Node<E> , pero no es una instancia de E ?

Mientras tanto, mi cerebro se está derritiendo ...

La clase anterior fue un ejemplo simplificado.
Para mostrar por qué necesito un auto-límite, estoy agregando un poco de complejidad:

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(); }


El problema no está en E root = (E) this . Puede funcionar bien hasta que comience a iterar a través del resultado de listNodes() .

Ese ejemplo demuestra dónde se lanzará exactamente ClassCastException :

public interface Node<E extends Node<E>> { List<E> getRelatedNodes(); default List<E> getAllNodes() { List<E> result = new ArrayList<>(); result.add((E) this); //<--that cast is not a problem because of type erasure return result; } } class NodeA implements Node<NodeA> { public NodeA() { } @Override public List<NodeA> getRelatedNodes() { return null; } } class NodeB implements Node<NodeA> { private List<NodeA> relatedNodes; public NodeB(List<NodeA> relatedNodes) { this.relatedNodes = relatedNodes; } @Override public List<NodeA> getRelatedNodes() { return relatedNodes; } }

Ejecutar:

List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects for (NodeA node : nodes) { //ClassCastException will be thrown System.out.println(node); }


Sin <E extends Node<E>> , podría tener cualquiera de estos casos:

Node<Integer>

donde el tipo genérico no es un Node en absoluto, o

Node<DifferentNode>

donde los límites genéricos no coinciden.

Dicho esto, no es típico ver un límite de esta manera, ya que se espera que el Node<E> sea ​​un nodo que contenga algún valor de tipo E , y que los children sean una List<Node<E>> , no una List<E> .


Un ejemplo sencillo para ilustrar el problema: un nodo de un tipo diferente de nodo:

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

Y:

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

En este caso, E root = (E) this resolvería a NodeA root = (NodeA) this , donde se trata de un NodeB . Y eso es incompatible.