Самостоятельная ссылка на дженерики Java: это безопасно? - PullRequest
0 голосов
/ 20 ноября 2018

У меня есть такой простой интерфейс:

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

Ответы [ 4 ]

0 голосов
/ 21 ноября 2018

В такой ситуации часто бывает полезно иметь метод 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> {
    ...
}

ХотяЯ мог бы не беспокоиться о наличии как абстрактного класса, так и интерфейсных слоев.

0 голосов
/ 20 ноября 2018

Простой пример, иллюстрирующий проблему: узел другого типа:

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

И:

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

В этом случае E root = (E) this разрешитNodeA root = (NodeA) this, где this является NodeB.И это несовместимо.

0 голосов
/ 20 ноября 2018

Проблема не в 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);
}
0 голосов
/ 20 ноября 2018

Без <E extends Node<E>> вы можете иметь один из следующих случаев:

Node<Integer>

, где универсальный тип вообще не равен Node, или

Node<DifferentNode>

, гдеобщие границы не совпадают.

Тем не менее, не типично видеть ограничение таким образом, поскольку ожидается, что Node<E> будет узлом, который содержит некоторое значение типа E, и children будет List<Node<E>>, а не List<E>.

...