Java 如何使用Google Guava创建具有不可变键且无重复项的地图?

Java 如何使用Google Guava创建具有不可变键且无重复项的地图?,java,guava,Java,Guava,我想使用Google Guava创建一个键/值映射结构,其中键不能修改,但值可以修改。我还希望能够使用谓词(或类似的东西)来迭代映射,并且只检索那些具有值的条目 例如,在概念上: // start Map data = {Constants.KEY_NAME_1, Optional.absent()}, {Constants.KEY_NAME_2, Optional.absent()}; // succeeds data.put(Constants.KEY_NAME_2, Optional.o

我想使用Google Guava创建一个键/值映射结构,其中键不能修改,但值可以修改。我还希望能够使用谓词(或类似的东西)来迭代映射,并且只检索那些具有值的条目

例如,在概念上:

// 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"));
你知道如何做到这一点吗

--------解决方案--------

根据下面的评论,我选择了ForwardingMap。实现非常简单

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;
    }
}
import com.google.common.collect.ImmutableMap;
导入com.google.common.collect.ForwardingMap;
导入com.google.common.collect.ImmutableList;
导入java.util.Map;
Map labelMap=ImmutableMap。建筑商()
.put(“键1”、“数据1”)
.put(“键2”、“数据2”)
.build();
MyCustomMap=新的MyCustomMap(labelMap);
公共类MyCustomMap扩展了ForwardingMap{
私人最终地图代表;
私有最终不可变映射不可变映射;
公共MyCustomMap(地图标签地图){
/*
在这里检查映射中是否存在重复值
上面的ImmutableMap确保不存在重复
钥匙。否则它会抛出
“IllegalArgumentException:具有相同密钥的多个条目”。
*/
委托=labelMap;
immutableMap=immutableMap.builder().putAll(委托).build();
}
@凌驾
受保护的映射委托(){
返回不可变映射;
}
}

如果你的钥匙不是一成不变的,番石榴不能为你做任何事;这是您必须自己确保的(通过确保所有键的类都是不可变的类)

即使是
ImmutableMap
也无法避免此类灾难:

// Modify the key
victim.keySet().iterator().next().alterMe();
如果您希望在插入/检索时自定义行为,则可以使用包装另一个
Map
实例


但是要注意,这个类给你留下了很多自由,包括打破
Map
契约的自由,这是你应该避免的

我认为番石榴不能帮你。Guava定义了
ImmutableMap
,这意味着既不能修改键也不能修改值。您所描述的更像是一个静态数组,而不是一个映射,其中数组位置映射到固定键。您最好编写自己的
Map
实现。您可以为键存储一个
ImmutableMap
,其中的值是实际映射值数组中的位置,例如
Value[]
,并用键集的大小初始化。然后,您可以实现自定义的
put
,如果提供的键不在
ImmutableMap
中,则会引发异常


或者,您可以为实现
put
的一些
Map
实现定义一个包装器。我将使用一个覆盖
put()
方法的
EnumMap

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

//TODO覆盖返回的映射中的
remove()
,这样它就不会删除条目,而是设置一个
可选的.empture()
。与其他可能影响地图的方法相同。

我无法理解最后一行失败的原因。你没告诉我什么?因为地图上没有键3。当试图把它放进去时,失败发生了。然后看看我的答案;您需要的是使用
转发地图
。感谢Federico和fge的回答。两者都很相似。在测试了这两个之后,我觉得ForwardingMap更适合。我把你的两个答案都记下来表示感谢。
// 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