在迭代完成后设置 Map.Entry 值?

Set Map.Entry value AFTER iteration complete?

我可以将 Map.Entry 个对象保存在临时 Map 中,然后在迭代完成后返回并更改它们的值吗?

例如,下面分两步实现一种交易。在第 1 步中,递归处理条目映射和条目映射(因此是一棵树)。条目被处理成新值,这些值被保存到一个临时映射中,由相应的 Map.Entry 键控。如果所有条目的计算都没有异常,则第 2 步只是简单地遍历临时映射并分配相应的新值。

void performPerilousProcedure(Object val) throws Exception
{
    if (processing of val fails)
        throw new Exception();
}
void recurivePerilousProcedure(Map someMap, Map tmp) throws Exception
{
    Iterator<Map.Entry<String,Object>> iter1;
    iter1 = someMap.entrySet().iterator();
    while (iter1.hasNext()) {
        Map.Entry<String,Object> entry1 = iter1.next();
        Object val = entry1.getValue();
        if (val instanceof Map) {
            recursivePerilousProcedure((Map)val, tmp);
        } else {
            Object newval = performPerilousProcedure(val);
            // ok to use Map.Entry as key across iter?
            tmp.put(entry1, newval);
        }   
    }   
}
void doit(Map<String,Object> someMap) throws Exception
{
    HashMap<Map.Entry<String,Object>,Object> tmp = new HashMap();
    // Try to process map of entries and maps of entries and ma ... 
    recursivePerilousProcedure(someMap, tmp);
    // All entries success processed, now simply assign new vals
    Iterator<Map.Entry<String,Object>> iter2;
    iter2 = tmp.keySet().iterator();
    while (iter2.hasNext()) {
        Map.Entry<String,Object> entry2 = iter2.next();
        Object newval = tmp.get(entry2);
        entry2.setValue(newval); // commit new value
    }   
}

问题是:

用作 tmp 键的 Map.Entry 对象能否在迭代之外存活?

假设没有并发修改,这是对 Map 接口的有效使用吗?

如果我跟着你,你可以这样做:

for (Entry<String, Object> entry : someMap.entrySet()) {
    Object newVal = performPerilousProcedure(entry.getValue());
    tmp.put(entry.getKey(), newVal);
}
    
someMap.putAll(tmp);

documentation of Map.Entry说的很清楚了:

These instances maintain a connection to the original, backing map. This connection to the backing map is valid only for the duration of iteration over the entry-set view. During iteration of the entry-set view, if supported by the backing map, a change to a Map.Entry's value via the setValue method will be visible in the backing map. The behavior of such a Map.Entry instance is undefined outside of iteration of the map's entry-set view.

未定义的行为并不妨碍执行预期的操作,实际上,当映射为 HashMapTreeMap 时,只要您不修改映射(与在条目上调用 setValue 的方式不同)。

问题是您是否要在这种实现特定行为的基础上构建您的应用程序,虽然长期存在,但尚未明确指定。


还有一个问题是您将这些条目用作另一个映射中的键。条目的相等性由键和值的组合决定,而不是由关联的映射决定。换句话说,如果不同映射的两个条目恰好具有相同的键和值,则会发生冲突,并且尝试放置另一个条目将保留已经存在的条目,但将其与不适用于该条目的新值相关联(或更精确,不适用于此目标地图)。

更糟糕的是,当条目实例已经用作键时,您正在更改相等性,方法是对它们调用 setValuewhich is definitely illegal 并将 HashMap 变成一个不一致状态:

Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.

如果您要做的只是迭代 tmp 一次,则无需使用地图,因为不需要查找操作。 List<Map.Entry<Key,Value>> 可以代替 Map<Key,Value>,以保持可以迭代的键和值的组合。事实上,任何能够保存两个值的对象都可以用来代替 Map.Entry<Key,Value>。如果用一个能够保存三个值的对象替换它,您可以跟踪目标 Map、键和新值,并在最后执行一个简单的 put

所以第一种方法看起来像

void recursivePerilousProcedure(Map<String,Object> someMap,
                                List<Map.Entry<Map.Entry<String,Object>,Object>> tmp)
                                throws Exception {
    for(Map.Entry<String,Object> entry1: someMap.entrySet()) {
        Object val = entry1.getValue();
        if (val instanceof Map) {
            recursivePerilousProcedure((Map<String,Object>)val, tmp);
        } else {
            Object newval = performPerilousProcedure(val);
            tmp.add(new AbstractMap.SimpleEntry<>(entry1, newval));
        }
    }
}
void doit(Map<String,Object> someMap) throws Exception {
    List<Map.Entry<Map.Entry<String,Object>,Object>> tmp = new ArrayList<>();

    // Try to process map of entries and record new values
    recursivePerilousProcedure(someMap, tmp);

    // All entries success processed, now simply assign new vals
    // relies on Map implementation detail
    for(Map.Entry<Map.Entry<String,Object>,Object> entry: tmp) {
        Object newval = entry.getValue();
        entry.getKey().setValue(newval); // assign new value
    }
}

它依赖于实现细节,但有一点优势,即调用 setValue 时不需要地图查找。或者干净的解决方案:

record NewValue(Map<String,Object> target, String key, Object newValue) {
    void apply() {
        target.put(key, newValue);
    }
}
void recursivePerilousProcedure(Map<String,Object> someMap,
                                List<NewValue> tmp) throws Exception {
    for(Map.Entry<String,Object> entry1: someMap.entrySet()) {
        Object val = entry1.getValue();
        if (val instanceof Map) {
            recursivePerilousProcedure((Map<String,Object>)val, tmp);
        } else {
            Object newval = performPerilousProcedure(val);
            tmp.add(new NewValue(someMap, entry1.getKey(), newval));
        }
    }
}
void doit(Map<String,Object> someMap) throws Exception {
    List<NewValue> tmp = new ArrayList<>();
    // Try to process map of entries and record new values
    recursivePerilousProcedure(someMap, tmp);

    // All entries success processed, now simply assign new vals
    // assign all new values, cleanly
    tmp.forEach(NewValue::apply);
}

根据您的应用,可能还有第三种选择。只需在递归处理过程中构建一个匹配预期目标结构的新映射,在没有错误发生时替换原始映射。