简体   繁体   English

Java:两个地图联合的只读视图?

[英]Java: read-only view of the union of two Maps?

Is there an implementation of java.util.Map in a common library (Guava, commons-collections, etc.) that serves as a read-only view of a union of two backing maps?在公共库(Guava、commons-collections 等)中是否有java.util.Map的实现,用作两个支持映射联合的只读视图? Ie, it would behave like union below, but without explicitly creating a new map instance, and reflecting future updates to the underlying maps:即,它的行为类似于下面的union ,但没有显式创建新的 map 实例,并反映未来对底层地图的更新:

Map<K, V> a, b; // From somewhere
Map<K, V> union = new HashMap<>();
union.putAll(b);
union.putAll(a);

My searching hasn't turned up anything.我的搜索没有任何结果。

When you implement it yourself, you can extends AbstractMap whose only mandatory method to implement, is entrySet() .当您自己实现它时,您可以扩展AbstractMap ,它的唯一强制实现方法是entrySet() Implementing the entry set by extending AbstractSet requires an implementation for iterator() and size() .通过扩展AbstractSet实现条目集需要实现iterator()size() That's the minimum required to implement a map.这是实现 map 所需的最低要求。

Since a key may appear in both maps, we can not simply sum up the map's sizes.由于两个地图中都可能出现一个键,我们不能简单地总结地图的大小。 So it has to be based on the same iteration logic as the iterator() implementation:所以它必须基于与iterator()实现相同的迭代逻辑:

  • take all entries from the first map从第一个 map 获取所有条目
  • take the entries from the second map whose key does not appear in the first one从第二个 map 获取其密钥未出现在第一个中的条目

Note that it's possible that the resulting size exceeds the int value range.请注意,结果大小可能超出int值范围。 There's no real solution to that.没有真正的解决方案。 Collection.size() specifies that Integer.MAX_VALUE will be returned when there are more elements, but I wouldn't be surprised when this policy turns out to be unknown to many developers. Collection.size()指定当元素更多时将返回Integer.MAX_VALUE ,但是当许多开发人员不知道这个策略时,我不会感到惊讶。

When implementing an iterator or similar facility handing out Map.Entry instances, we have to take care not to use the original map's entries, as that would allow modifying the original map through the entry's setValue method.在实现迭代器或类似工具分发Map.Entry实例时,我们必须注意不要使用原始地图的条目,因为这将允许通过条目的setValue方法修改原始 map。

The iteration logic can be expressed concisely using the Stream API:使用 Stream API 可以简明地表达迭代逻辑:

public class MergedMap<K,V> extends AbstractMap<K,V> {
    final Map<K,V> first, second;

    public MergedMap(Map<K, V> first, Map<K, V> second) {
        this.first = Objects.requireNonNull(first);
        this.second = Objects.requireNonNull(second);
    }

    // mandatory methods

    final Set<Map.Entry<K, V>> entrySet = new AbstractSet<Map.Entry<K, V>>() {
        @Override
        public Iterator<Map.Entry<K, V>> iterator() {
            return stream().iterator();
        }

        @Override
        public int size() {
            long size = stream().count();
            return size <= Integer.MAX_VALUE? (int)size: Integer.MAX_VALUE;
        }

        @Override
        public Stream<Entry<K, V>> stream() {
            return Stream.concat(first.entrySet().stream(), secondStream())
            .map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), e.getValue()));
        }

        @Override
        public Stream<Entry<K, V>> parallelStream() {
            return stream().parallel();
        }

        @Override
        public Spliterator<Entry<K, V>> spliterator() {
            return stream().spliterator();
        }
    };

    Stream<Entry<K, V>> secondStream() {
        return second.entrySet().stream().filter(e -> !first.containsKey(e.getKey()));
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        return entrySet;
    }

    // optimizations

    @Override
    public boolean containsKey(Object key) {
        return first.containsKey(key) || second.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return first.containsValue(value) ||
            secondStream().anyMatch(Predicate.isEqual(value));
    }

    @Override
    public V get(Object key) {
        V v = first.get(key);
        return v != null || first.containsKey(key)? v: second.get(key);
    }

    @Override
    public V getOrDefault(Object key, V defaultValue) {
        V v = first.get(key);
        return v != null || first.containsKey(key)? v:
            second.getOrDefault(key, defaultValue);
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        first.forEach(action);
        second.forEach((k,v) -> { if(!first.containsKey(k)) action.accept(k, v); });
    }
}

Since it implements the iteration logic via the Stream API, the entry set also overrides the Stream related methods providing that Stream directly. Since it implements the iteration logic via the Stream API, the entry set also overrides the Stream related methods providing that Stream directly. This avoids inheriting implementations resorting to the iterator actually backed by a Stream.这避免了继承实现诉诸由 Stream 实际支持的迭代器。

Further, the map itself implements the common query methods for efficiency.此外,map 本身实现了常见的查询方法以提高效率。 The logic is similar, use the first map's entry if present, the second map's otherwise.逻辑类似,如果存在则使用第一个地图的条目,否则使用第二个地图的条目。 Special attention is required to the possibility to have a key explicitly mapped to null , as some maps allow.需要特别注意将键显式映射到null的可能性,因为某些映射允许。 The methods above use the optimistic approach, when a non- null value is present, only a single lookup is required.上述方法使用乐观方法,当存在非null值时,只需要一次查找。

I quite like Holger's answer, but here's a simpler approach that is easily understandable and debuggable.我非常喜欢 Holger 的回答,但这是一种更简单的方法,易于理解和调试。 It delegates all read only methods to the union that is calculated on demand:它将所有只读方法委托给按需计算的联合:

public class UnionMap<K,V> extends AbstractMap<K,V> {
    private Map<K,V> a, b;

    public UnionMap(Map<K, V> a, Map<K, V> b) {
        this.a = Objects.requireNonNull(a);
        this.b = Objects.requireNonNull(b);
    }

    private Map<K,V> union() {
        Map<K, V> union = new HashMap<>(b);
        union.putAll(a);
        return union;
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        return union().entrySet();
    };

    @Override
    public boolean containsKey(Object key) {
        return union().containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return union().containsValue(value);
    }

    @Override
    public V get(Object key) {
        return union().get(key);
    }

    @Override
    public V getOrDefault(Object key, V defaultValue) {
        return union().getOrDefault(key, defaultValue);
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        union().forEach(action);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM