Java泛型自引用:安全吗?

时间:2018-11-20 19:15:25

标签: java generics this self-reference

我有这个简单的界面:

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 :(得分:12)

一个简单的例子来说明问题:一个不同类型节点的节点:

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

并且:

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

在这种情况下,E root = (E) this将解析为NodeA root = (NodeA) this,其中thisNodeB。那是不兼容的。

答案 1 :(得分:2)

没有<E extends Node<E>>,您可能会遇到以下两种情况之一:

Node<Integer>

泛型类型根本不是Node,或者

Node<DifferentNode>

通用范围不匹配的地方。

也就是说,通常以这种方式查看绑定是 ,因为Node<E>被认为是包含类型为{{ 1}},而E将是children,而不是List<Node<E>>

答案 2 :(得分:2)

问题不在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);
}

答案 3 :(得分:1)

在这种情况下,通常有一个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> {
    ...
}

尽管我可能不介意同时拥有抽象类和接口层。