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.

+2


source to share


2 answers


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 modifying ImmutableMap

    using the map they passed into.

+4


source


you can limit the size of your map

In riding mode, you can use



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

      

0


source







All Articles