代码之家  ›  专栏  ›  技术社区  ›  Michele Mariotti

Java泛型自我引用:安全吗?

  •  16
  • Michele Mariotti  · 技术社区  · 6 年前

    我有一个简单的界面:

    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> (根据定义)。
    但我无法想象一个案例 不是的实例 E
    自从 E extends Node<E> 不该 节点& e; 也相当于 e 根据定义??

    你能举一个对象的例子吗? 节点& 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 回复  |  直到 6 年前
        1
  •  12
  •   ernest_k Petronella    6 年前

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

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

    以及:

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

    在这种情况下, E root = (E) this 会下决心 NodeA root = (NodeA) this 在哪里 this 是一个 NodeB .这是不相容的。

        2
  •  2
  •   chrylis -cautiouslyoptimistic-    6 年前

    没有 <E extends Node<E>> ,您可以有以下任一情况:

    Node<Integer>
    

    其中泛型类型不是 Node 总之,或者

    Node<DifferentNode>
    

    一般界限不匹配的地方。

    也就是说, 典型的情况是看到这样一个界限,如 Node<E> 应为 包含 类型的某个值 E children 将是一个 List<Node<E>> 不是 List<E> .

        3
  •  2
  •   Aleksandr Semyannikov    6 年前

    问题不在 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
  •  1
  •   Theodore Norvell    6 年前

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

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