简体   繁体   English

如何创建Java Map <String,String> 带有不可修改的密钥?

[英]How to create a Java Map<String,String> with unmodifiable keys?

In java, how should I create a Map<String,String> that has unmodifiable keys, while keeping the values modifiable. 在Java中,我应该如何创建一个具有不可修改键的Map<String,String> ,同时保持值的可修改性。

I'd like to hand this Map<String,String> through an interface for someone else to add/change the Map values, but not be able to change the Map keys. 我想通过接口将此Map<String,String>交给其他人添加/更改Map值,但不能更改Map键。

The background on higher level problem is that I have list/set of variable names (with tree like structure) (represented as java String) that I'd like code on the other side of the java interface to be able to populate aliases (also Strings) for each of the variable names. 更高级别问题的背景是我有一个变量名列表/集(具有树状结构)(表示为Java字符串),我希望Java接口另一侧的代码能够填充别名(也每个变量名的字符串)。 I'd like to have multiple implementations of this interface so naming tree hierarchy can be aliases different ways to fit different situations. 我希望此接口具有多个实现,因此命名树层次结构可以作为别名来适应不同情况的不同方式。 Having the interface implementation populate a Map<String,String> with bunch of keys already set-in-stone (and maybe containing defaults for the values) and allowing it to modify the values (but not the keys), seems like the best approach. 让接口实现使用已经设置好的键堆填充Map<String,String> (可能包含值的默认值)并允许其修改值(而不是键),这似乎是最好的方法。 I'm creating a mapping between names and alias, so Map<> makes sense. 我正在创建名称和别名之间的映射,因此Map<>有意义。

Back to the lower level problem. 回到较低层次的问题。 I'd like my code to resemble: 我希望我的代码类似于:

    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
        }
    }

I know there is Collections unmodifiableMap(Map<> m) but that also makes the values unmodifiable. 我知道有Collections unmodifiableMap(Map<> m)但这也使值无法修改。 If my values were mutable objects, then I could modify them but I'd like to stick with Strings (avoiding creating Class with set/get for single String member, or creating Structure-like-class with public String member). 如果我的值是可变对象,那么我可以对其进行修改,但我想坚持使用Strings (避免使用set / get为单个String成员创建Class,或者使用public String成员创建类似Structure的类)。

AKA, I'd like to avoid creating my own mutable class-values, and use Collections unmodifiableMap() to make the keys and value references unmodifiable: 再次,我想避免创建自己的可变类值,并使用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
    }

I could extend or implement my own Map, then (like how Collections unmodifiableMap() ) override all methods that could change the keys throw UnsupportedOperationException . 我可以扩展或实现自己的Map,然后(例如Collections unmodifiableMap() )覆盖所有可能更改键抛出UnsupportedOperationException But with Java 8, there has been a large number of methods added using Lambda functions, which would be nice for Interface implementers to have access to, as long as they could not change the keys. 但是在Java 8中,使用Lambda函数添加了许多方法,只要接口实现者无法更改键,它们就可以很好地访问接口实现者。

AKA, I'd like to avoid this lengthy and error-prone technique: 又名,我想避免这种冗长且容易出错的技术:

    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 }
    }

Is there existing interface that only allows changing the data of values of Maps<> ? 是否存在仅允许更改Maps<>的值数据的接口?

What are my other options for creating something resembling a Map<String,String> that has unmodifiable keys , but modifiable values ? 我还有什么其他选择来创建类似于Map<String,String>东西,这些东西具有无法修改的键 ,但是具有可修改的值 I am interested in good coding practices, if possible. 如果可能,我对良好的编码习惯感兴趣。

Seems like you're looking for the Proxy Pattern . 似乎您在寻找Proxy Pattern


Detailed answer: 详细答案:

The idea is to use what's called a proxy to interact with the map. 这个想法是使用所谓的代理与地图进行交互。 The proxy will intercept all calls to the map; 代理将拦截对地图的所有调用; you should only be able to interact with the map through the proxy. 您应该只能通过代理与地图进行交互。 It acts as an interface between the client and the map. 它充当客户端和地图之间的接口。

A proxy is a skeleton of what you are "wrapping". 代理是您“包装”内容的骨架。 Since you are creating a proxy for a map, the proxy should implement the Map interface: 由于您正在为地图创建代理,因此代理应实现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
}

Most methods will simply telescope to map . 大多数方法将仅通过望远镜进行map Modify the methods you need to prevent removing keys or adding new keys to the map, such as put , putAll and remove : 修改您需要防止删除键或向地图添加新键的方法,例如putputAllremove

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

Keynotes: 主题演讲:

  1. Collections.unmodifiableSet should be used when returning sets from the map. 从地图返回集合时,应使用Collections.unmodifiableSet This ensures that if a person attempts to modify a set returned from the map, it'll throw an UnsupportedOperationException 这样可以确保如果某人尝试修改从地图返回的集合,则会抛出UnsupportedOperationException

  2. Creating a new Map containing the values of the map passed into the constructor prevents the client from modifying the ImmutableMap using the map they passed into it. 创建一个包含传递给构造函数的映射值的新Map阻止客户端使用传递给它的映射来修改ImmutableMap

you may want to limit the size of your map 您可能想限制地图的大小

In over riding your put method you may use 在过度使用推杆方法时,您可以使用

if (map.size() == maxEntries) {
             throw some exception;

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

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