为什么我不能通过“for”循环保持EnumMap条目,即使我使用“final”?最好的解决方法?

时间:2011-12-25 15:42:18

标签: java for-loop final anonymous-inner-class

我有一些奇怪的行为。

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

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

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

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

更新: 我可能不得不让我的代码工作,无论它是针对Java 6还是7编译的,那么在任何一种情况下使其工作的最有效的解决方法是什么?

6 个答案:

答案 0 :(得分:3)

认为我已经弄明白这里发生了什么,final关键字无关。如果您从此行中删除final修饰符:

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

exact same behavior发生。

但是这是什么?来自the Map.Entry JavaDocs(强调补充):

  

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

...我读作&#34;不要尝试在Map.Entry&#34;返回的集合视图上使用迭代之外的Map.entrySet个对象。 - 这会调用未定义的行为。


This answer更详细地解释了它。 OP看到的问题与EnumMap Map.Entry的实施有关。

答案 1 :(得分:2)

对于EnumMapIdentityHashMap,这是Java 6及更早版本中的行为。这是为了表现而做的(来源 - Josh Bloch,13:56,“Java Puzzlers”,2011年5月(Voo在评论中给出的原始视频链接))。它不再发生在Java 7中,现在您可以依赖于收集的Map.Entry对象,而不管后续迭代。

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

克隆它
new AbstractMap.SimpleImmutableEntry(entry)

并改为使用它。

答案 2 :(得分:1)

好的,我进入了源代码 - 当发送keySet,valueSet和entrySet时,EnumMap做了一件非常奇怪的事情。来自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'定义了单次迭代的行为。如果您将它传递给内部类,请在类入口点定义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){}

就像那样。

答案 3 :(得分: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

答案 4 :(得分: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

看到这篇文章:Iterating over EnumMap#entrySet它会解释一些事情。

答案 5 :(得分:0)

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

for ( VariableModifiersopt Type Identifier: Expression) Statement

完全等同于:

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

(其中IExpression.iterator()的类型)。在您的情况下,VariableModifiersoptfinal。从等效表单中可以清楚地看出,final仅适用于Statement中变量的使用。