如何创建Java Map< String,String>用不可修改的键? [英] How to create a Java Map<String,String> with unmodifiable keys?

查看:171
本文介绍了如何创建Java Map< String,String>用不可修改的键?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在java中,如何创建一个具有不可修改密钥的 Map< String,String> ,同时保持值可修改。 p>

我想通过一个界面来交给这个 Map< String,String> ,让别人添加/更改地图值,但不能更改地图键。



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



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

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

IMyMapper mapper;

//如何使用它
void work()
{
映射< String,String>地图
//魔术的东西像Collections unmodifiableMap,但只用于键
//也许我的问题应该是UnmodifiableMap的这个魔法如何工作,所以我可以重现吗?
mapper.build(map);
//因为Maps<>通过引用,改变它们(到值)将被反映在这里
}
}

public class TheyClass实现MyClass.IMyMapper
{
@Override
public void build(Map< String,String>映射)
{
//使用映射,如Map< String,String>没有额外/外部类
//但是无法修改Map键
//只能更改Map值
//应该能够使用所有的awesome Map东西,像foreach,值,计算
}
}

我知道那里是 Collections unmodifiableMap(Map<> m),但这也使得值不可修改。如果我的值是可变对象,那么我可以修改它们,但是我想坚持使用 Strings (避免使用set / get为单个String成员创建Class,或者创建Structure与公共String成员一样的类)。



AKA,我想避免创建自己的可变类值,并使用集合unmodifiableMap()使键和值引用不可修改:

  //对String的变量引用
public class ExtraWorkForForEveryone
{
public String value;

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

//和然后使用:
void work()
{
映射< String,ExtraWorkForEveryone>地图;
map = Collections.unmodifiableMap(...)
//因为Collections.unmodifiableMap()只阻止他们改变Map引用,
//接口可能还会改变ExtraWorkForEveryone内部。
//所以他们无法更改键参考或值参考,但是他们可以更改值数据。
mapper.build(map);
//因为Maps<>通过引用,他们所做的更改(对价值)将在这里反映
}

我可以扩展或实现我自己的地图,然后(像如何 Collections unmodifiableMap())覆盖所有可以改变键的方法throw UnsupportedOperationException 。但是对于Java 8来说,使用Lambda函数添加了大量方法,对于Interface实现者来说,只要不能更改密钥,这将是很好的。



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

  public final class FinalHashMap extends HashMap 
{
@Override //任何可能可以更改地图关键字的内容
so_many_methods_and_edge_cases()
{throws UnsupportedOperationException}
}

现有的界面是否只允许更改 Maps的值的数据



我的其他选项是创建类似于 Map< String,String> 不可修改的密钥,但可修改的值?如果可能,我对良好的编码实践感兴趣。

解决方案

似乎你正在寻找代理模式






详细答案:



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



代理是一个骨架,您正在包装。由于您正在为地图创建代理,因此代理应实现 Map 界面:

 code> class ImmutableMap< K,V>实现Map< K,V> {
private Map< K,V>地图;

public ImmutableMap(Map< K,V> map){
this.map = new HashMap<(map); //分离参考
}

//从地图
中实现方法

大多数方法将简单地望向 map 。修改您需要的方法,以防止删除键或向地图添加新的键,例如 put putAll 删除

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

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新的IllegalArgumentException(无法添加新密钥!);
}

return map.put(key,value);
}

@Override
public V remove(Object key){
throw new UnsupportedOperationException(你不能从此地图中删除条目!


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

This.map.putAll(map);
}

@Override
public void clear(){
throw new UnsupportedOperationException(您不能从此地图中删除条目!);
}

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

@Override
public Collection< V> values(){
return Collections.unmodifiableSet(map.values()); // prevebt将值更改为null
}

@Override
public Set< Map.Entry< K,V>> entrySet(){
//允许修改值,创建自己的(immutable)条目集,并返回
return Collections.unmodifiableSet(map.entrySet());
}
}

主题演讲:


$当从地图返回集合时,应该使用Collections.unmodifiableSet
,而


  1. 这确保如果一个人尝试修改从地图返回的集合,它将抛出一个 UnsupportedOperationException


  2. 创建新的 Map ,其中包含传递到构造函数中的映射值阻止客户端修改 ImmutableMap 使用他们传入的地图。



In java, how should I create a Map<String,String> that has unmodifiable keys, while keeping the values modifiable.

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.

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. 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. I'm creating a mapping between names and alias, so Map<> makes sense.

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. 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).

AKA, I'd like to avoid creating my own mutable class-values, and use Collections unmodifiableMap() to make the keys and value references unmodifiable:

    // 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. 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.

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

What are my other options for creating something resembling a Map<String,String> that has unmodifiable keys, but modifiable values? I am interested in good coding practices, if possible.

解决方案

Seems like you're looking for the 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:

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. Modify the methods you need to prevent removing keys or adding new keys to the map, such as put, putAll and remove:

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. This ensures that if a person attempts to modify a set returned from the map, it'll throw an 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.

这篇关于如何创建Java Map&lt; String,String&gt;用不可修改的键?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆