4

我有一些奇怪的行为。

[更新:给出完整的可运行示例:]

package finaltestwithenummapentry;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map.Entry;

public class FinalTestWithEnumMapEntry {

    enum SomeEnum{
        ONE, TWO, THREE;
    }

    public static void main(String[] args) {
        EnumMap<SomeEnum, Integer> map = new EnumMap<SomeEnum, Integer>(SomeEnum.class);
        map.put(SomeEnum.ONE, 1);
        map.put(SomeEnum.TWO, 2);
        map.put(SomeEnum.THREE, 3);

        ArrayList<Entry<SomeEnum, Integer>> entryList = new ArrayList<Entry<SomeEnum, Integer>>();  

        for(final Entry<SomeEnum, Integer> entry : map.entrySet()){      
            System.out.println("Key is " + entry.getKey() + ", value is " + entry.getValue());     
            //This prints the correct keys and values      

            entryList.add(entry); 
        }  

        System.out.println("");

        for(Entry<SomeEnum, Integer> entry:entryList){     
            System.out.println("Key is " + entry.getKey() + ", value is " + entry.getValue());     
            //This prints only the last entry each time 
        }
    }
}

输出(JavaSE 1.6):

Key is ONE, value is 1
Key is TWO, value is 2
Key is THREE, value is 3

Key is THREE, value is 3
Key is THREE, value is 3
Key is THREE, value is 3

我假设是最终的My entry,似乎每次都被下一个覆盖。我每次都需要能够捕获正确的条目,因为我将每个项目传递给for循环内的匿名内部类实例。

[更新:Java 7 中不存在此问题,只有 Java 6(可能更早)]

更新:无论是针对 Java 6 还是 7 编译,我都可能必须让我的代码工作,那么在这两种情况下让它工作的最有效的解决方法是什么?

4

6 回答 6

3

我已经弄清楚这里发生了什么,它与关键字无关。final如果您final从此行中删除修饰符:

for(final Entry<SomeEnum, Integer> entry : map.entrySet()){      

发生完全相同的行为

但这是什么?来自JavaDocs Map.Entry强调添加):

映射条目(键值对)。该Map.entrySet方法返回地图的集合视图,其元素属于此类。获取对映射条目的引用的唯一方法是从此集合视图的迭代器。这些Map.Entry对象仅在迭代期间有效。

...我读为“不要尝试在Map.Entry由”返回的集合视图上使用迭代之外的对象Map.entrySet——这会调用未定义的行为。


这个答案更详细地解释了它。OP 看到的问题EnumMapMap.Entry.

于 2011-12-25T17:25:47.663 回答
2

这是 Java 6 和更早版本中的一种行为EnumMapIdentityHashMap。这样做是为了提高性能(来源 - Josh Bloch, 13: 56,“Java Puzzlers”,2011 年 5 月(Voo 在评论中给出的原始视频链接))。从 Java 7 开始不再发生这种情况,您现在可以依赖收集的 Map.Entry 对象,而不管后续迭代如何。

如果您打算在下一次迭代发生后使用条目,最好的解决方法是通过克隆它

new AbstractMap.SimpleImmutableEntry(entry)

并改用它。

于 2011-12-25T23:48:53.950 回答
1

好的,我深入研究了源代码 - EnumMap 在发送 keySet、valueSet 和 entrySet 时做了一件非常奇怪的事情。来自 JDK 源代码:

/**
 * Since we don't use Entry objects, we use the Iterator itself as entry.
 */
private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>>
        implements Map.Entry<K,V>
{
    public Map.Entry<K,V> next() {
        if (!hasNext())
            throw new NoSuchElementException();
        lastReturnedIndex = index++;
        return this;
    }

他们在 EnumMap 中有一堆自定义类,它们在迭代期间返回值,因此他们不希望您在该迭代器之外使用它。

这就是为什么它的行为与其他 Map.Entry 不同的原因。您的解决方案是保留/创建一个单独的地图/列表并用键/值填充它,而不是保留 Entry 对象。

(下面的旧答案)

在这种情况下,“最终”定义了单次迭代的行为。如果要将其传递给内部类,请在类入口点定义 final :

for(final Entry<K, V> entry : map.entrySet()){ 
  System.out.println("Key is " + entry.getKey() + ", value is " + entry.getValue());
  //This prints the correct keys and values
  doSomething(entry);
}

private void doSomething(final Entry<K,V> entry){}

像那样。

于 2011-12-25T15:46:05.793 回答
1

你这是什么意思?

//This prints only the last entry each time

使用这段代码(Java 7):

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

public class ForeachLoopWithFinal {

enum MyEnum {
    ONE("one"),
    TWO("two"),
    THREE("three");

    private String name;

    private MyEnum(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }

}

public static void main(String[] args) {
    List<Map.Entry<MyEnum, Integer>> entryList = new ArrayList<>();
    Map<MyEnum, Integer> map = new EnumMap<>(MyEnum.class);

    map.put(MyEnum.ONE, 1);
    map.put(MyEnum.TWO, 2);
    map.put(MyEnum.THREE, 3);

    for (final Map.Entry<MyEnum, Integer> entry : map.entrySet()) {
        System.out.printf("Key is %s, value is %s%n", entry.getKey(), entry.getValue());
        entryList.add(entry);
    }

    for (Map.Entry<MyEnum, Integer> entry : entryList) {
        System.out.printf("Key is %s, value is %s%n", entry.getKey(), entry.getValue());
    }
}

}

我得到了这个输出。因此,第一次和第二次打印完全相同数量的条目。

Key is one, value is 1
Key is two, value is 2
Key is three, value is 3
Key is one, value is 1
Key is two, value is 2
Key is three, value is 3

使用您的示例,我得到了相同的结果。

Key is ONE, value is 1
Key is TWO, value is 2
Key is THREE, value is 3

Key is ONE, value is 1
Key is TWO, value is 2
Key is THREE, value is 3
于 2011-12-25T16:10:13.233 回答
0

我相信这是因为每次发生循环时都会创建您的“条目”,因此它实际上每次都是一个新变量。如果您尝试在循环内设置其他内容,我相信您会收到编译器错误。

编辑,根据反馈:一定有你没有向我们展示的东西。

Map<String, String> map = new HashMap<String, String>();
        map.put("barf", "turd");
        map.put("car", "bar");
        ArrayList<Entry<String, String>> entryList = new ArrayList<Entry<String, String>>();

        //Assuming I have a Map<K,V> called "map":
        for(final Entry<String, String> entry : map.entrySet()){ 
            System.out.println("Key is " + entry.getKey() + ", value is " + entry.getValue());
            //This prints the correct keys and values

            entryList.add(entry);
        }

        for(Entry<String,String> entry:entryList){
            System.out.println("Key is " + entry.getKey() + ", value is " + entry.getValue());
            //This prints only the last entry each time
        }

打印出来:

    Key is car, value is bar
    Key is barf, value is turd
    Key is car, value is bar
Key is barf, value is turd

请参阅这篇文章:迭代 EnumMap#entrySet它将解释一些事情。

于 2011-12-25T15:44:37.853 回答
0

根据Java Language Specificationfor ,使用如下所示的迭代器时的增强循环:

for ( VariableModifiersopt Type Identifier: Expression) Statement

完全等同于:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
        VariableModifiersopt Type Identifier = #i.next();
   Statement
}

(其中I的类型是Expression.iterator())。在你的情况下,VariableModifiersoptfinal。从等价形式可以清楚地看出,final仅适用于 内的变量的使用Statement

于 2011-12-25T16:58:17.930 回答