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