【问题标题】:Generics trouble implementing Map.entrySet()实现 Map.entrySet() 的泛型问题
【发布时间】:2013-07-15 20:21:53
【问题描述】:

我正在尝试扩展AbstractMap 以创建MapTreeNode 类(通过键而不是索引访问子节点的树节点)。

我已经有一种方法可以让一组孩子正常工作:

public class MapTreeNode<K,V> implements Map.Entry<K,V> {
    private Map<K,MapTreeNode<K,V>> children = new HashMap<K,MapTreeNode<K,V>>();
    private Set<MapTreeNode<K,V>> child_set = null;

    public Set<MapTreeNode<K,V>> children() {
        if (child_set == null)
            child_set = new ChildSet();

        return child_set;
    }

    ...

    private final class ChildSet extends AbstractSet<MapTreeNode<K,V>> {
        @Override
        public Iterator<MapTreeNode<K,V>> iterator() {
            return children.values().iterator();
        }

        @Override
        public int size() {
            return MapTreeNode.this.childCount();
        }
        ...
    }

}

我想创建一个节点的地图视图 (Map&lt;K,V&gt;) 并重用 child_set,但我不确定 Java 的泛型是否可行:

public Map<K,V> asMap() {
    return new AbstractMap<K,V>() {
        @Override
        public Set<Map.Entry<K,V>> entrySet() {
            return child_set; // line 166
        }
    };
}

这当然给了

MapTreeNode:166: incompatible types
found   : java.util.Set<MapTreeNode<K,V>>
required: java.util.Set<java.util.MapEntry<K,V>>

有没有办法我可以为此重用我的 ChildSet 类?

【问题讨论】:

标签: java generics map runtime-type


【解决方案1】:

问题在于entrySet() 的返回类型。它是Set&lt;Map.Entry&lt;K,V&gt;&gt;。如您所知,Foo&lt;A&gt;Foo&lt;B&gt; 不兼容,用于不同的 A 和 B,无论它们如何相关。

我认为这是 API 中的设计错误。 entrySet() 的返回类型实际上应该是 Set&lt;? extends Map.Entry&lt;K,V&gt;&gt;。原因如下:如果您阅读documentation for entrySet(),它表示可以从 Set 中读取内容,可以从 Set 中删除内容(这会导致底层映射发生更改),但无法将内容添加到集。这完全符合 Producer 的角色——您无需向其添加任何内容。根据 PECS 规则,应使用 extends-wildcard 集合类型。

【讨论】:

    【解决方案2】:

    除非您需要 MapTreeNode 方法中的特定内容,否则将其视为 Map.Entry,这意味着将 child_set 声明为

    private Set<Map.Entry<K,V>> child_set = null;
    

    既然MapTreeNode 扩展了Map.Entry,你应该没问题。

    【讨论】:

    • 我试图避免在 MapTreeNode 课程的其余部分中将所有内容都转换为 MapTreeNode,但也许这是唯一可行的选择。
    • 诀窍是,如果 MapTreeNode 仅用于替换 Map.Entry 的内部功能,则在将数据添加到 Map 时将您的条目转换为 MapTreeNode,并留下 @987654332 @ 对外界不可见。如果您需要一个只有MapTreeNode 可以提供的全新方法,那么我们可以解决其他问题。
    • MapTreeNodeDefaultMutableTreeNode 的替代品,因此我删除了许多其他功能以避免发布代码墙。
    • 永远张贴代码墙,布拉德!将你所拥有的东西扔进我的编译器并填补空白已经够难了……
    • 全部贴出来不太实用,重点是它需要是公共类,而且我需要知道对象是MapTreeNodes,而不仅仅是Map.Entrys .所以问题归结为“我可以在不更改entrySet() 的返回类型的情况下将Set&lt;MapTreeNode&gt; 作为Set&lt;Map.Entry&gt; 返回吗?”
    【解决方案3】:

    这是迄今为止我在避免代码重复方面能做的最好的事情:

    private abstract class AbstractChildSet<T extends Map.Entry<K,V>> extends AbstractSet<T> {
        @Override
        public boolean remove(Object o) {
            if (o == null || !(o instanceof Map.Entry)) {
                return false;
            }
    
            MapTreeNode<K,V> node;
            if (o instanceof MapTreeNode)
                node = (MapTreeNode<K,V>) o;
            else
                node = MapTreeNode.this.child(((Map.Entry<K,V>) o).getKey());
    
            if (node == null || !isParentOf(node))
                return false;
    
            node.removeFromParent();
            return true;
        }
    
        @Override
        public int size() {
            return MapTreeNode.this.childCount();
        }
    
        @Override
        public void clear() {
            MapTreeNode.this.removeAllChildren();
        }
    }
    
    private final class ChildSet extends AbstractChildSet<MapTreeNode<K,V>> {
        @Override       
        public boolean add(MapTreeNode<K,V> node) {
            if (MapTreeNode.this.containsKey(node.getKey()))
                return false;
    
            MapTreeNode.this.addChild(node);
            return true;
        }
    
        @Override
        public Iterator<MapTreeNode<K,V>> iterator() {
            return children.values().iterator();
        }
    }
    
    private final class EntrySet extends AbstractChildSet<Map.Entry<K,V>> {
        @Override
        public boolean add(Map.Entry<K,V> entry) {
            if (MapTreeNode.this.containsKey(entry.getKey()))
                return false;
    
            MapTreeNode new_child = new HashMapTreeNode(MapTreeNode.this, entry.getKey(), entry.getValue());
    
            MapTreeNode.this.addChild(new_child);
            return true;
        }
    
        @Override
        public Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
    }
    

    【讨论】:

      【解决方案4】:

      据我所知,您已经实现了新类来获取条目类型的子项。要再次构建地图,我将遍历一组条目并重建地图。

      不确定我是否像其他人一样在这里提供帮助,我无法完全阅读该课程包含的其他内容。

      【讨论】:

        【解决方案5】:

        你为什么不像这样简单地投射它:

        public Map<K,V> asMap() {
            return (Map<K,V>) this;
        }
        

        【讨论】:

        • Map 中的一些方法不能很好地满足树节点的需求,因此MapTreeNode 没有直接实现Map 接口。此外,我在其他几个地方也遇到过这个问题,所以我正在寻找一种通用的方法来解决这个问题。
        • MapTreeNode 没有实现Map
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-19
        • 2015-09-29
        • 1970-01-01
        • 1970-01-01
        • 2015-04-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多