如何生成由列表支持的地图
How to generate a Map backed by a List
我有一个如下所示的数据模型:
class CustomField {
String key;
String value;
}
从 API 我可以获得 List<CustomField>
的实例。键在列表中是唯一的,这意味着这个集合确实应该是一个Map<String, String>
。在此列表上操作很痛苦,因为每个操作都需要迭代来检查现有键(CustomField 也没有实现 equals)
如何创建一个由该列表支持的 Map<String, String>
"view",以便我可以使用 Map 接口对其进行操作?
我想要一个通用方法,例如:<T, K, V> Map<K, V> createMapBackedByList(List<T> list, BiFunction<K, V, T> elementMapper, Function<T, K> keyMapper, Function<T, V> valueMapper)
或类似方法。
它将使用函数在列表元素之间映射并映射键和值。
这里重要的是我希望对地图的更改反映在底层列表中,这就是流 API 在这里不起作用的原因...
编辑:我无法修改 API 或 CustomField class。
简单:
自己写
public class ListBackedMap<K, V> implements Map<K, V> {
创建时需要某种 List<Pair<K,V>>
; "defers" 到那个。当然,这需要您的 CustomField
class 实现 Pair
接口。 (你可能也需要发明)
(或者:您的新 class extends AbstractMap<K,V>
可以保护您的大部分工作)。
现在您的方法只是 return 此类 Map 的一个实例。
换句话说:我不知道有满足您要求的内置包装器。但是自己实施一个应该非常简单。
编辑:考虑到 OP 无法更改 CustomField
class,一个简单的助手,例如
interface <K, V> MapEntryAdapter {
K getKey();
V getValue();
}
需要;以及知道如何从 CustomField
的实例中检索 key/value 的特定实现。在这种情况下,地图将由 List<MapEntryAdapter<K, V>>
支持。
我最终尝试自己实现它并将其基于 AbstractList。它实际上比我第一次更容易...
public class ListBackedMap<T, K, V> extends AbstractMap<K, V> {
private final List<T> list;
private final BiFunction<K, V, T> keyValueToElement;
private final Function<T, K> elementToKey;
private final Function<T, V> elementToValue;
public ListBackedMap(List<T> list, BiFunction<K, V, T> keyValueToElement, Function<T, K> elementToKey, Function<T, V> elementToValue) {
this.list = list;
this.keyValueToElement = keyValueToElement;
this.elementToKey = elementToKey;
this.elementToValue = elementToValue;
}
@Override
public Set<Entry<K, V>> entrySet() {
return list.stream()
.collect(toMap(elementToKey, elementToValue))
.entrySet();
}
@Override
public V put(K key, V value) {
V previousValue = remove(key);
list.add(keyValueToElement.apply(key, value));
return previousValue;
}
public List<T> getList() {
return list;
}
}
它的性能不是很好(或线程安全),但它似乎可以很好地完成工作。
示例:
List<CustomField> list = getList();
ListBackedMap<CustomField, String, String> map = new ListBackedMap<>(
list,
(key, value) -> new CustomField(key, value),
CustomField::getKey,
CustomField::getValue);
我有一个如下所示的数据模型:
class CustomField {
String key;
String value;
}
从 API 我可以获得 List<CustomField>
的实例。键在列表中是唯一的,这意味着这个集合确实应该是一个Map<String, String>
。在此列表上操作很痛苦,因为每个操作都需要迭代来检查现有键(CustomField 也没有实现 equals)
如何创建一个由该列表支持的 Map<String, String>
"view",以便我可以使用 Map 接口对其进行操作?
我想要一个通用方法,例如:<T, K, V> Map<K, V> createMapBackedByList(List<T> list, BiFunction<K, V, T> elementMapper, Function<T, K> keyMapper, Function<T, V> valueMapper)
或类似方法。
它将使用函数在列表元素之间映射并映射键和值。
这里重要的是我希望对地图的更改反映在底层列表中,这就是流 API 在这里不起作用的原因...
编辑:我无法修改 API 或 CustomField class。
简单:
自己写
public class ListBackedMap<K, V> implements Map<K, V> {
创建时需要某种 List<Pair<K,V>>
; "defers" 到那个。当然,这需要您的 CustomField
class 实现 Pair
接口。 (你可能也需要发明)
(或者:您的新 class extends AbstractMap<K,V>
可以保护您的大部分工作)。
现在您的方法只是 return 此类 Map 的一个实例。
换句话说:我不知道有满足您要求的内置包装器。但是自己实施一个应该非常简单。
编辑:考虑到 OP 无法更改 CustomField
class,一个简单的助手,例如
interface <K, V> MapEntryAdapter {
K getKey();
V getValue();
}
需要;以及知道如何从 CustomField
的实例中检索 key/value 的特定实现。在这种情况下,地图将由 List<MapEntryAdapter<K, V>>
支持。
我最终尝试自己实现它并将其基于 AbstractList。它实际上比我第一次更容易...
public class ListBackedMap<T, K, V> extends AbstractMap<K, V> {
private final List<T> list;
private final BiFunction<K, V, T> keyValueToElement;
private final Function<T, K> elementToKey;
private final Function<T, V> elementToValue;
public ListBackedMap(List<T> list, BiFunction<K, V, T> keyValueToElement, Function<T, K> elementToKey, Function<T, V> elementToValue) {
this.list = list;
this.keyValueToElement = keyValueToElement;
this.elementToKey = elementToKey;
this.elementToValue = elementToValue;
}
@Override
public Set<Entry<K, V>> entrySet() {
return list.stream()
.collect(toMap(elementToKey, elementToValue))
.entrySet();
}
@Override
public V put(K key, V value) {
V previousValue = remove(key);
list.add(keyValueToElement.apply(key, value));
return previousValue;
}
public List<T> getList() {
return list;
}
}
它的性能不是很好(或线程安全),但它似乎可以很好地完成工作。
示例:
List<CustomField> list = getList();
ListBackedMap<CustomField, String, String> map = new ListBackedMap<>(
list,
(key, value) -> new CustomField(key, value),
CustomField::getKey,
CustomField::getValue);