如何使用不可修改的键创建 Java Map<String,String>?
How to create a Java Map<String,String> with unmodifiable keys?
在 java 中,我应该如何创建一个具有不可修改键的 Map<String,String>
,同时保持值可修改。
我想通过一个界面将此 Map<String,String>
交给其他人 add/change 地图值,但无法更改地图键。
更高级别问题的背景是我有 list/set 个变量名(具有树状结构)(表示为 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
(避免为单个 String 成员创建 Class 和 set/get,或创建类似结构的- class 和 public 字符串成员)。
AKA,我想避免创建自己的可变 class 值,并使用 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 函数的方法,接口实现者可以访问这些方法很好,只要他们不能更改键即可。
又名,我想避免这种冗长且容易出错的技术:
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<>
值数据的现有接口?
我还有哪些其他选项可以创建类似于 Map<String,String>
的内容,其中包含 不可修改的键 ,但 可修改的值 ?如果可能的话,我对良好的编码实践很感兴趣。
您似乎在寻找 Proxy Pattern.
详细答案:
想法是使用所谓的 代理 与地图进行交互。代理会拦截所有对地图的调用;您应该只能通过代理与地图进行交互。它充当客户端和地图之间的接口。
代理是您的骨架 "wrapping"。由于您正在为地图创建代理,因此代理应实现 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
、putAll
和 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());
}
}
主题演讲:
从地图返回集时应使用 Collections.unmodifiableSet
。这确保如果有人试图修改从地图返回的集合,它会抛出 UnsupportedOperationException
创建一个包含传递给构造函数的映射值的新 Map
可防止客户端使用传递给它的映射修改 ImmutableMap
。
您可能想要限制地图的大小
在超越你的 put 方法时,你可以使用
if (map.size() == maxEntries) {
throw some exception;
在 java 中,我应该如何创建一个具有不可修改键的 Map<String,String>
,同时保持值可修改。
我想通过一个界面将此 Map<String,String>
交给其他人 add/change 地图值,但无法更改地图键。
更高级别问题的背景是我有 list/set 个变量名(具有树状结构)(表示为 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
(避免为单个 String 成员创建 Class 和 set/get,或创建类似结构的- class 和 public 字符串成员)。
AKA,我想避免创建自己的可变 class 值,并使用 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 函数的方法,接口实现者可以访问这些方法很好,只要他们不能更改键即可。
又名,我想避免这种冗长且容易出错的技术:
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<>
值数据的现有接口?
我还有哪些其他选项可以创建类似于 Map<String,String>
的内容,其中包含 不可修改的键 ,但 可修改的值 ?如果可能的话,我对良好的编码实践很感兴趣。
您似乎在寻找 Proxy Pattern.
详细答案:
想法是使用所谓的 代理 与地图进行交互。代理会拦截所有对地图的调用;您应该只能通过代理与地图进行交互。它充当客户端和地图之间的接口。
代理是您的骨架 "wrapping"。由于您正在为地图创建代理,因此代理应实现 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
、putAll
和 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());
}
}
主题演讲:
-
从地图返回集时应使用
Collections.unmodifiableSet
。这确保如果有人试图修改从地图返回的集合,它会抛出UnsupportedOperationException
创建一个包含传递给构造函数的映射值的新
Map
可防止客户端使用传递给它的映射修改ImmutableMap
。
您可能想要限制地图的大小
在超越你的 put 方法时,你可以使用
if (map.size() == maxEntries) {
throw some exception;