如何使用 Google 番石榴创建具有不可变键且无重复项的地图?
How to create a Map with immutable keys and no duplicates using Google Guava?
我想使用 Google Guava 创建一个 key/value 映射结构,其中键不能修改,但值可以。我还希望能够使用谓词(或类似的东西)来迭代 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;
}
}
如果你的密钥不是不可变的,Guava 就不能为你做任何事情;这是您必须确保自己的事情(通过确保所有键的 class 是不可变的 class)。
即使是 ImmutableMap
也不能幸免于这种事故:
// Modify the key
victim.keySet().iterator().next().alterMe();
如果您想在 insertion/retrieval 上自定义行为,那么您可以使用 ForwardingMap
包装另一个 Map
实例。
但是请注意,这 class 会给您带来很大的自由,包括违反 Map
合同的自由,您显然应该避免这样做!
我认为 Guava 无法为您做到这一点。 Guava 定义了 ImmutableMap
,这意味着键和值都不能被修改。您所描述的更像是一个静态数组而不是地图,其中数组位置映射到固定键。您最好编写自己的 Map
实现。您可以为键存储一个 ImmutableMap<Key,Integer>
,其中值是实际地图值数组中的位置,例如 Value[]
,使用键集的大小进行初始化。然后你可以实现你的自定义 put
如果提供的键不在 ImmutableMap
.
中则抛出异常
或者您可以只为实现 put
.
的某些 Map
实现定义包装器
我会使用 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
。它使用匿名 class' 初始化程序块将提供的键映射到 Optional.absent()
并覆盖 put
禁止将未作为参数提供的键放入的方法。
它还有一个辅助方法,returns 包含值不同于 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()
。与其他可能影响地图的方法相同。
我想使用 Google Guava 创建一个 key/value 映射结构,其中键不能修改,但值可以。我还希望能够使用谓词(或类似的东西)来迭代 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;
}
}
如果你的密钥不是不可变的,Guava 就不能为你做任何事情;这是您必须确保自己的事情(通过确保所有键的 class 是不可变的 class)。
即使是 ImmutableMap
也不能幸免于这种事故:
// Modify the key
victim.keySet().iterator().next().alterMe();
如果您想在 insertion/retrieval 上自定义行为,那么您可以使用 ForwardingMap
包装另一个 Map
实例。
但是请注意,这 class 会给您带来很大的自由,包括违反 Map
合同的自由,您显然应该避免这样做!
我认为 Guava 无法为您做到这一点。 Guava 定义了 ImmutableMap
,这意味着键和值都不能被修改。您所描述的更像是一个静态数组而不是地图,其中数组位置映射到固定键。您最好编写自己的 Map
实现。您可以为键存储一个 ImmutableMap<Key,Integer>
,其中值是实际地图值数组中的位置,例如 Value[]
,使用键集的大小进行初始化。然后你可以实现你的自定义 put
如果提供的键不在 ImmutableMap
.
或者您可以只为实现 put
.
Map
实现定义包装器
我会使用 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
。它使用匿名 class' 初始化程序块将提供的键映射到 Optional.absent()
并覆盖 put
禁止将未作为参数提供的键放入的方法。
它还有一个辅助方法,returns 包含值不同于 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()
。与其他可能影响地图的方法相同。