How can I create a map with immutable keys and no duplicates using Google Guava?

I would like to create a key / value map structure using Google Guava where keys cannot be changed, but values ​​can. I also want to be able to use a predicate (or something similar) to iterate over the map and only get the records that matter.

For example, conceptually:

// start
Map data =
{Constants.KEY_NAME_1, Optional.absent()},
{Constants.KEY_NAME_2, Optional.absent()};

// succeeds
data.put(Constants.KEY_NAME_2, Optional.of("new_data"));

// finish
Map data =
{Constants.KEY_NAME_1, Optional.absent()},
{Constants.KEY_NAME_2, Optional("new_data")};

// fails
data.put(Constants.KEY_NAME_3, Optional.of("more_new_data"));

      

Any idea how to do this?

-------- Decision --------

As per the comments below, I went with ForwardingMap. The implementation is simple.

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableList;
import java.util.Map;

Map<String, String> labelMap = ImmutableMap.<String, String> builder()
    .put("KEY_1", "data1")
    .put("KEY_2", "data2")
    .build();

MyCustomMap<String> map = new MyCustomMap(labelMap);

public class MyCustomMap<String> extends ForwardingMap<String, String> {

    private final Map<String, String> delegate;
    private final ImmutableMap<String, String> immutableMap;

    public MyCustomMap(Map<String, String> labelMap) {

        /*
            Check for duplicate values in the map here.  The construction of 
            the ImmutableMap above ensures that there are no duplicate
            keys.  Otherwise it will throw
            "IllegalArgumentException: Multiple entries with same key".
        */

        delegate = labelMap;
        immutableMap = ImmutableMap.<String, String>builder().putAll(delegate).build();
    }

    @Override
    protected Map<String, String> delegate() {
        return immutableMap;
    }
}

      

+3


source to share


3 answers


Guava can do nothing for you if your keys are not immutable; this is something you should be sure of yourself (making sure the class of all keys is an immutable class).

Not ImmutableMap

even immune to this kind of failure:

// Modify the key
victim.keySet().iterator().next().alterMe();

      



If you want to customize the insert / extract behavior, you can use ForwardingMap

another Map

instance to transfer .

Beware, however, that this class leaves you a lot of freedom, including breaking the contract Map

, which you obviously should refrain from!

+2


source


I would use EnumMap

which overwrites the method put()

:

public enum Constants {
    KEY_NAME_1, KEY_NAME_2, KEY_NAME_3;

    @SuppressWarnings("serial")
    public static <T> EnumMap<Constants, Optional<T>> asMap(
        final Constants... validKeys) {

        return new EnumMap<Constants, Optional<T>>(Constants.class) {
            {
                for (Constants c : validKeys) {
                    super.put(c, Optional.absent());
                }
            }

            @Override
            public Optional<T> put(Constants key, Optional<T> value) {
                if (!this.containsKey(key)) {
                    throw new IllegalArgumentException("Invalid key");
                }
                return super.put(key, value);
            }
        };
    }

    public static <T> Map<Constants, Optional<T>> withValues(
        EnumMap<Constants, Optional<T>> map) {

        return Maps.filterValues(map, new Predicate<Optional<T>>() {

            @Override
            public boolean apply(Optional<T> input) {
                return input.isPresent();
            }
        });
    }
}

      

This is enum

with a static method that creates an anonymous one EnumMap

, initialized with the supplied keys. It uses the anonymous class initializer block to map the supplied keys to Optional.absent()

and overrides the put

method to deny entry of keys not supplied as arguments.

It also has a helper method that returns a view of the map containing records whose values ​​differ from Optional.absent()

.



Sample usage:

// Create map with KEY_NAME_1 and KEY_NAME_2 only
EnumMap<Constants, Optional<String>> map = 
    Constants.asMap(Constants.KEY_NAME_1, Constants.KEY_NAME_2);

System.out.println(map); // {KEY_NAME_1=Optional.absent(), KEY_NAME_2=Optional.absent()}

map.put(Constants.KEY_NAME_2, Optional.of("two"));

System.out.println(map); // {KEY_NAME_1=Optional.absent(), KEY_NAME_2=Optional.of(two)}

Map<Constants, Optional<String>> withValues = Constants.withValues(map);

System.out.println(withValues); // {KEY_NAME_2=Optional.of(two)}

map.put(Constants.KEY_NAME_3, Optional.of("three")); // throws IllegalArgumentException

      

// TODO Override remove()

in the returned map so that instead of deleting the entry, it installs Optional.absent()

. It's the same with other methods that can affect the map.

+1


source


I don't think Guava can do this for you. Guava defines ImmutableMap

which means that neither keys nor values ​​can be changed. What you are describing is more like a static array than a map where array positions are mapped to fixed keys. You might be better off writing your own implementation Map

. You can store ImmutableMap<Key,Integer>

for keys where the values ​​are positions in an array of actual map values, for example Value[]

, initialized with the size of the keyset. Then you can implement your own put

, which will throw an exception if the provided key is not in ImmutableMap

.

Or, you can simply define a wrapper for some implementation Map

that implements put

.

0


source







All Articles