Самостоятельная ссылка на дженерики Java: это безопасно?
У меня есть этот простой интерфейс:
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;
}
}
Я вижу, что this
всегда экземпляр Node<E>
(по определению).
Но я не могу представить себе случай, когда this
не пример E
...
Поскольку E extends Node<E>
, разве Node<E>
также не должен быть эквивалентным E
по определению??
Можете ли вы привести пример объекта, который является экземпляром Node<E>
, но это не экземпляр E
??
Тем временем мой мозг тает...
Предыдущий класс был упрощенным примером.
Чтобы показать, почему я нуждаюсь в самоограничении, добавлю немного сложности:
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();
}
Ответы
Ответ 1
Простой пример, иллюстрирующий проблему: узел другого типа:
class NodeA implements Node<NodeA> {
...
}
А также:
class NodeB implements Node<NodeA> {
...
}
В этом случае E root = (E) this
разрешило бы NodeA root = (NodeA) this
, где this
NodeB
. И это несовместимо.
Ответ 2
Без <E extends Node<E>>
вы можете получить любой из этих случаев:
Node<Integer>
где универсальный тип вообще не является Node
, или
Node<DifferentNode>
где общие границы не совпадают.
Тем не менее, это не типично видеть границу таким образом, поскольку ожидается, что Node<E>
будет узлом, который содержит некоторое значение типа E
, а children
будет List<Node<E>>
, а не List<E>
Ответ 3
Проблема не в E root = (E) this
. Это может хорошо работать до тех пор, пока вы не начнете перебирать результат listNodes()
.
Этот пример демонстрирует, где именно будет выброшено 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;
}
}
Выполнение:
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);
}
Ответ 4
В такой ситуации часто полезно иметь метод getThis
который (по соглашению) возвращает this
.
Я бы сделал следующее
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> {
...
}
Хотя, возможно, я не буду беспокоиться о наличии как абстрактных классов, так и слоев интерфейса.