Why can't I store EnumMap entries through a "for" loop, even if I use "final"? Best workaround?

I have strange behavior.

[UPDATE: full example cited:]

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

Output (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 , which I assume is final, seems to be rewritten next each time. I need to be able to commit the correct entry every time since I pass each element to an anonymous internal instance of the class inside the for loop.

[UPDATE: this problem does not exist in Java 7, only Java 6 (and possibly earlier)]

UPDATE: I might have to get my code to work regardless of whether it was compiled against Java 6 or 7, so what is the most effective workaround for it to work anyway?

+4
source share
6 answers

This behavior is in Java 6 and earlier for EnumMap and IdentityHashMap . This was done for performance's sake (source - Josh Bloch, 13:56 , "Java Puzzlers", May 2011 (original video link provided by Voo in the comment)). This no longer happens with Java 7, where you can now rely on assembled Map.Entry objects, regardless of subsequent iterations.

The best workaround if you intend to use a record after the next iteration is to clone it with

 new AbstractMap.SimpleImmutableEntry(entry) 

and use it instead.

+2
source

I think I understand what is happening here, and it has nothing to do with the final keyword. If you remove the final modifier from this line:

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

exact same behavior .

But what is it? From Map.Entry JavaDocs (highlighted by me):

Card recording (key-value pair). The Map.entrySet method returns a view of the map collection whose elements belong to this class. The only way to get a link to a map entry is through an iterator of this type of collection. These Map.Entry objects Map.Entry valid only for the duration of the iteration.

... which I read as "I am not trying to use Map.Entry objects outside of iteration in the collection view returned by Map.entrySet " - causes undefined behavior. >


This answer explains this in more detail. The problem that the OP sees is related specifically to the EnumMap implementation of Map.Entry .

+3
source

OK, I went to the source code - EnumMap does a very strange thing when it comes to sending keys, value sets and entrySet. Download Source Package 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; } 

They have a bunch of custom classes in EnumMap that return a value during the iteration, so they don’t want you to use it outside of this iterator.

That's why it behaves differently than other Map.Entry. The solution for you is to save / create a separate map / list and fill it with keys / values ​​instead of saving Entry objects.

(old answer below)

'final' in this case defines this behavior for one iteration. If you pass it to an inner class, define final at the class entry point:

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

Like this.

+1
source

What do you mean by that?

 //This prints only the last entry each time 

Using this piece of code (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()); } } 

}

I get exactly this conclusion. Thus, the same number of entries are printed for the first and second time.

 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 

Using your example, I get the same thing.

 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 
+1
source

I believe because your “record” is created every time a loop happens, so this is actually a new variable every time. If you tried to set the record to something else inside the loop, I believe that you will get a compiler error.

Change based on feedback: there must be something you don’t show us.

 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 } 

produces:

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

See this post: Iterating over EnumMap # entrySet This will explain everything.

0
source

According to the Java Language Specification, an extended for loop when using an iterator that looks like this:

 for ( VariableModifiersopt Type Identifier: Expression) Statement 

exactly equivalent to:

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

(where I is the type of Expression.iterator() ). In your case, VariableModifiersopt is final . As you can see from the equivalent form, final applies only to the use of the variable inside the Statement .

0
source

Source: https://habr.com/ru/post/1387943/


All Articles