TERACytE TERACytE - 22 days ago 9
Java Question

How to create a Map with immutable keys and no duplicates using Google Guava?

I'd like to create a key/value map structure using Google Guava where the keys cannot be modified but the values can. I also want the ability to use a predicate (or something similar) to iterate the Map and only retrieve those entries that have values.

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

-------- Solution --------

As per the comments below, I went with the ForwardingMap. The Implementation is straightforward

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;
}
}

fge fge
Answer

Guava cannot do anything for you if your keys are not immutable; this is something you have to ensure yourself (by making sure that the class of all keys is an immutable class).

Even an ImmutableMap is not immune to this kind of mishap:

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

If what you wish to do is customize the behavior upon insertion/retrieval then you can use a ForwardingMap to wrap another Map instance.

Beware however that this class leaves you a lot of freedom, including that of breaking the Map contract, which you should obviously refrain from!