如何使用Google Guava创建具有不可变键的Map并且没有重复项?

时间:2015-07-23 16:19:38

标签: java guava

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

例如,概念上:

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

3 个答案:

答案 0 :(得分:2)

如果你的钥匙不是一成不变的,那么番石榴对你无能为力;这是你必须确保自己的东西(通过确保所有键的类是一个不可变的类)。

即使ImmutableMap也不能幸免于这种不幸事件:

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

如果你想要做的是在插入/检索时自定义行为,那么你可以使用ForwardingMap来包装另一个Map实例。

请注意,这个课程给你留下了很大的自由,包括打破Map合同,你应该明确地避免这个合同!

答案 1 :(得分:1)

我使用覆盖EnumMap方法的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();
            }
        });
    }
}

这是一个enum,它带有一个静态方法,可以创建一个使用提供的键初始化的匿名EnumMap。它使用匿名类'初始化程序块将提供的键映射到Optional.absent()并覆盖put 禁止将未提供的密钥作为参数提供的方法。

它还有一个辅助方法,它返回一个包含值不同于Optional.absent()的条目的地图视图。

样本用法:

// 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覆盖返回的地图中的remove(),这样就不会删除条目,而是设置Optional.absent()。与可能影响地图的其他方法相同。

答案 2 :(得分:0)

我不认为番石榴可以为你做到这一点。 Guava定义ImmutableMap,这意味着密钥和值都不能被修改。您所描述的更像是静态数组而不是地图,其中数组位置映射到固定键。编写自己的Map实现可能会更好。您可以为键存储ImmutableMap<Key,Integer>,其中值是实际地图值数组中的位置,例如Value[],使用键集的大小进行初始化。然后,如果提供的密钥不在put中,您可以实现引发异常的自定义ImmutableMap

或者您可以为实现Map的某些put实现定义包装器。