泛型实现Map.entrySet()的麻烦

时间:2013-07-15 20:21:54

标签: java generics map runtime-type

我正在尝试扩展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<K,V>)的地图视图并重用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课程呢?

5 个答案:

答案 0 :(得分:3)

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

我认为这是API中的设计错误。 entrySet()的返回类型应该是Set<? extends Map.Entry<K,V>>。原因如下:如果您阅读entrySet()的{​​{3}},它表示可以从Set中读取内容,可以从Set中删除内容(这会导致更改底层地图),但是无法添加到Set中。这完全符合制作人的角色 - 你不会添加东西。根据PECS规则,应使用extends - 通配符集合类型。

答案 1 :(得分:1)

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

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

由于MapTreeNode延伸Map.Entry,您应该没问题。

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

我明白你已经实现了新的类来获得类型为Entry的子项。为了再次构建一个地图,我将遍历一组条目并重建地图。

我不确定我是否像其他人一样帮助我,我无法完全阅读此课程中包含的内容。

答案 4 :(得分:-1)

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

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