2

在java中,我应该如何创建一个Map<String,String>具有不可修改键的,同时保持值可修改。

我想Map<String,String>通过一个界面将其交给其他人添加/更改 Map 值,但不能更改 Map 键。

更高级别问题的背景是我有变量名列表/集(具有树状结构)(表示为 java 字符串),我希望 java 接口另一侧的代码能够填充别名(也字符串)用于每个变量名称。我想有这个接口的多个实现,所以命名树层次结构可以是别名不同的方式来适应不同的情况。让接口实现填充Map<String,String>一堆已经设置好的键(并且可能包含值的默认值)并允许它修改值(但不是键),似乎是最好的方法。我正在创建名称和别名之间的映射,所以Map<>很有意义。

回到较低层次的问题。我希望我的代码类似于:

    public class MyClass
    {
        public interface IMyMapper
        {
            void build(Map<String,String> mapping);
        }

        IMyMapper mapper;

        // How I'd like to use it
        void work()
        {
            Map<String,String> map ;
            // Magic something like Collections unmodifiableMap, but only for keys
            // Maybe my question should be how this magic for UnmodifiableMap works, so I could reproduce it??
            mapper.build(map); 
            // Because Maps<> are by reference, changed they made (to the values) would be reflected here
        }
    }

    public class TheirClass implements MyClass.IMyMapper
    {
        @Override
        public void build(Map<String,String> mapping)
        {
            // use mapping like Map<String,String> without extra/foreign classes
            // but not be able to modify the Map keys
            // only be able to change the Map values
            // Should be able to use all of the awesome Map stuff, like foreach, values, compute
        }
    }

我知道有,Collections unmodifiableMap(Map<> m)但这也使值不可修改。如果我的值是可变对象,那么我可以修改它们,但我想坚持使用Strings(避免使用 set/get 为单个字符串成员创建类,或使用公共字符串成员创建类结构类)。

AKA,我想避免创建我自己的可变类值,并用于Collections unmodifiableMap()使键和value references不可修改:

    // mutable reference to a String
    public class ExtraWorkForForEveryone
    {
        public String value;

        public void setValue(String value) { ... }
        public String getValue() { ... }
    }

    // and then use:
    void work()
    {            
        Map<String,ExtraWorkForEveryone> map;
        map = Collections.unmodifiableMap( ... );
        // because Collections.unmodifiableMap() only stops them from changing the Map references,
        // the interfacer could still change the ExtraWorkForEveryone internals.
        // so they could not change keys refs or value refs, but they could change value data.
        mapper.build(map); 
        // Because Maps<> are by reference, changed they made (to the values) would be reflected here
    }

我可以扩展或实现我自己的 Map,然后(比如如何Collections unmodifiableMap())覆盖所有可以更改键 throw 的方法UnsupportedOperationException。但是在 Java 8 中,已经添加了大量使用 Lambda 函数的方法,这对于接口实现者来说是很好的,只要他们不能更改键即可。

AKA,我想避免这种冗长且容易出错的技术:

    public final class FinalHashMap extends HashMap
    {
        @Override // anything that might be able to change the Map Keys
        so_many_methods_and_edge_cases()
        { throws UnsupportedOperationException }
    }

是否存在仅允许更改值的数据的接口Maps<>

我还有什么其他选择可以创建类似于 aMap<String,String>的具有不可修改键可修改值的东西?如果可能的话,我对良好的编码实践感兴趣。

4

2 回答 2

4

好像您正在寻找Proxy Pattern


详细解答:

这个想法是使用所谓的代理与地图进行交互。代理会拦截所有对地图的调用;您应该只能通过代理与地图进行交互。它充当客户端和地图之间的接口。

代理是您“包装”的内容的骨架。由于您正在为地图创建代理,因此代理应实现Map接口:

class ImmutableMap<K, V> implements Map<K, V> {
    private Map<K, V> map;

    public ImmutableMap(Map<K, V> map) {
        this.map = new HashMap<>(map); // detach reference
    }

    //implement methods from Map
}

大多数方法将简单地望远镜到map. 修改您需要的方法以防止删除键或向映射添加新键,例如put,putAllremove:

final class ImmutableMap<K, V> implementsMap<K, V> {
    private Map<K, V> map;

    public ImmutableMap(Map<K, V> map) {
        this.map = new HashMap<>(map);
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

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

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

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

    @Override
    public V put(K key, V value) {
        if(!map.containsKey(key)) {
            throw new IllegalArgumentException("Cannot add new keys!");
        }

        return map.put(key, value);
    }

    @Override
    public V remove(Object key) {
        throw new UnsupportedOperationException("You cannot remove entries from this map!");
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        for(K key : map.keySet()) {
            if(!this.map.containsKey(key)) {
                throw new IllegalArgumentException("Cannot add new keys to this map!");
            }
        }

        this.map.putAll(map);
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException("You cannot remove entries from this map!");
    }

    @Override
    public Set<K> keySet() {
        return Collections.unmodifiableSet(map.keySet());
    }

    @Override
    public Collection<V> values() {
        return Collections.unmodifiableSet(map.values()); //prevebt changing values to null
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        //to allow modification of values, create your own ("immutable") entry set and return that
        return Collections.unmodifiableSet(map.entrySet()); 
    }
}

主题演讲:

  1. Collections.unmodifiableSet从地图返回集合时应使用。这确保了如果一个人试图修改从地图返回的集合,它会抛出一个UnsupportedOperationException

  2. 创建一个Map包含传递给构造函数的映射值的新对象可以防止客户端修改ImmutableMap使用他们传递给它的映射。

于 2015-04-09T20:14:22.640 回答
0

您可能想限制地图的大小

在过度使用您的 put 方法时,您可以使用

if (map.size() == maxEntries) {
             throw some exception;
于 2015-04-09T20:28:50.370 回答