How do I create a Java map <String, String> with unmodifiable keys?
In java, how do I create Map<String,String>
one that has unmodifiable keys while keeping the values mutable.
I would like to pass this Map<String,String>
through the interface in order for someone else to add / change the map values, but fail to change the Map keys.
The backstory of a higher level problem is that I have a list / set of variable names (with a tree-like structure) (represented as a java string) that I would like the code on the other side of the Java interface to be able to populate aliases (also strings) for each of the variable names. I would like to have multiple implementations of this interface so that the tree naming hierarchy can be aliased for different situations. If the interface implementation is populating Map<String,String>
with a set of keys already set in stone (and possibly containing default values for the values) and allowing it to change values (but not keys) seems like the best approach. I am creating a mapping between names and aliases, so Map<>
it makes sense.
Let's go back to the lower level problem. I would 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 unmodifiable values. If my values were mutable objects, then I could modify them, but I would like to stick with Strings
(avoiding creating a class with set / get on a single String member, or creating a class of type Structure with a public String member).
AKA, I would 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 can change the throw keys UnsupportedOperationException
. But since Java 8, a large number of methods have been added that use Lambda functions, which would be nice if front-end implementations could access them if they couldn't change keys.
AKA, I would 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 an existing interface that allows the value data to be changed Maps<>
?
What are my other options for making something similar to Map<String,String>
one that has unmodifiable keys but mutable values ? I'm interested in good coding techniques if possible.
source to share
It looks like you are looking for a proxy pattern .
Detailed answer:
The idea is to use what is called a proxy to interact with the map. The proxy server will intercept all calls to the card; you must be able to interact with the map through a proxy. It acts as an interface between the client and the card.
The proxy is the skeleton of what you "wrap" around. Since you are creating a proxy for the map, the proxy must implement the 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 up to Map
. Modify the methods as needed to prevent keys from being deleted or new keys added 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());
}
}
leitmotifs:
-
Collections.unmodifiableSet
should be used when returning sets from a card. This ensures that if the person tries to change the set returned from the card, they will rollUnsupportedOperationException
-
Creating a new
Map
one containing the values of the map passed to the constructor prevents the client from modifyingImmutableMap
using the map they passed into.
source to share