设置地图。迭代完成后输入值?

发布于 2025-01-31 17:46:13 字数 1627 浏览 5 评论 0 原文

我可以在临时地图中保存地图。输入对象,然后在迭代完成后返回并更改其值吗?

例如,以下以两个步骤实现了一种交易。在步骤1中,递归处理条目的条目和条目地图(因此,一棵树)。条目被处理为新值,这些值将保存到临时地图,并由相应的地图键入。如果无一例外地计算所有条目,则步骤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
    }   
}

问题是:

map.entry 用作 tmp 的对象可以在迭代外生存吗?

只要没有并发修改,这是MAP接口的有效使用吗?

Can I save Map.Entry objects in a temporary Map and then go back and change their values AFTER iteration has completed?

For example, the following implements a sort of transaction in two steps. In step 1, a map of entries and maps of entries (so a tree of sorts) is recursively processed. Entries are processed into new values which are saved to a temporary map, keyed by the corresponding Map.Entry. If all entries are computed without Exception, step 2 is to simply iterate over the temporary map and assign the corresponding new value.

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

The question is:

Can the Map.Entry objects used as keys of tmp survive outside the iterations?

Provided that there are no concurrent modifications, is this a valid use of the Map interface?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

落墨 2025-02-07 17:46:13

map.entry 对此非常清楚:

这些实例保持了与原始背景图的连接。在入门集视图上,在迭代持续时间内,与背映射的此连接仅是有效的 。在入门集视图的迭代期间,如果由备用映射支持,则通过 map.entry 的值更改” https://docs.oracle.com/ en/java/javase/17/docs/api/java.base/java/util/map.entry.html#setValue(v)“ rel =“ nofollow noreferrer”> setValue 方法将在背景图中可见。此类 map.entry 实例的行为是在地图入门集视图的迭代之外未定义的。

未定义的行为并不排除执行预期的事情,在实践中,当地图为 hashmap treemap 时,只要您不修改该实例地图(以不同的方式与调用 setValue 在条目上)。

问题是您是否要在这种实施特定的行为上构建应用程序,尽管长期以来尚未明确指定该应用程序。


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

更糟糕的是,您正在通过在输入实例中用作密钥时更改平等,通过在它们上调用 setValue 绝对是非法的,然后将 hashmap 转换为一个不一致的状态:

注意:如果将可变的物体用作地图键,则必须谨慎行事。如果对象的值以影响 equals 比较的方式更改对象的值时,未指定地图的行为。

如果您要做的一切,就是要迭代 tmp 一次,就不需要使用地图,因为不需要查找操作。 list&lt; map.entry&lt; key,value&gt;&gt; 将做代替 map&lt; key,value&gt; ,以保持密钥和值的组合,您可以迭代遍历。实际上,可以使用任何能够持有两个值的对象代替 map.entry&lt; key,value&gt; 。如果将其替换为能够持有三个值的对象,则可以跟踪目标地图,键和新值,然后在末尾执行简单的 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);
}

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

The documentation of Map.Entry is very clear about it:

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.

An undefined behavior does not preclude doing the intended thing and in practice, when the map is HashMap or TreeMap, these entry instances keep working as long as you don’t modify the the map (in a different way than calling setValue on an entry).

The question is whether you want to build your application on such an implementation specific behavior which, while longstanding, has not been specified explicitly.


There’s another problem in that you are using these entries as keys in another map. The equality of an entry is determined by the combination of key and value, but not the associated map. In other words, if two entries of different maps happen to have the same key and value, you have a clash and trying to put the other entry will keep the already existing entry but associate it with the new value not meant for this entry (or more precise, not meant for this target map).

Even worse, you are changing the equality while the entry instance is already in use as a key, by calling setValue on them, which is definitely illegal and turns the HashMap into an inconsistent state:

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.

If all that you are going to do, is to iterate over tmp once, there is no need to use a map as no lookup operation is required. A List<Map.Entry<Key,Value>> would do instead of Map<Key,Value>, to keep a combination of key and value you can iterate over. In fact, any object capable of holding two values could be used instead of Map.Entry<Key,Value>. If you replace it with an object capable of holding three values, you could keep track of the target Map, key, and new value and just perform a simple put at the end.

So the first approach could look like

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

which is relying on the implementation detail but has the slight advantage that no map lookup is needed when calling setValue. Or the clean solution:

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

Depending on your application, there might be a third option. Just build a new map matching the intended target structure during the recursive processing, to replace the original one when no error occurred.

野心澎湃 2025-02-07 17:46:13

如果我跟随,您可以做这样的事情:

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

If I'm following along, you can do something like this:

for (Entry<String, Object> entry : someMap.entrySet()) {
    Object newVal = performPerilousProcedure(entry.getValue());
    tmp.put(entry.getKey(), newVal);
}
    
someMap.putAll(tmp);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文