Почему я не могу держать записи EnumMap через цикл for, даже если я использую «final»?Лучший обходной путь? - PullRequest
4 голосов
/ 25 декабря 2011

У меня странное поведение.

[ОБНОВЛЕНИЕ: приведен полный пример выполнения:]

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, так какой же самый эффективный обходной путь для его работы в любом случае?1015 *

Ответы [ 6 ]

3 голосов
/ 25 декабря 2011

Я думаю Я выяснил, что здесь происходит, и не имеет ничего общего с ключевым словом final. Если вы удалите из этого модификатор finalстрока:

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

происходит точно такое же поведение .

Но что это?С Map.Entry JavaDocs (выделение добавлено):

Запись карты (пара ключ-значение).Метод Map.entrySet возвращает коллекционное представление карты, элементы которой принадлежат этому классу. только способ получить ссылку на запись карты - от итератора этого представления коллекции. Эти Map.Entry объекты действительны только на время итерации.

... которые я читал как "не пытайтесь использовать Map.Entry объекты внеитерация в представлении коллекции, возвращаемом Map.entrySet "- , это вызывает неопределенное поведение.


Этот ответ объясняет его более подробно.Проблема, которую видит ОП, связана именно с реализацией EnumMap Map.Entry.

2 голосов
/ 26 декабря 2011

Это поведение в Java 6 и более ранних версиях для EnumMap и IdentityHashMap.Это было сделано ради производительности (источник - Джош Блох, 13: 56 , «Java Puzzlers», май 2011 г. (оригинальная ссылка на видео предоставлена ​​Voo в комментарии)).Это больше не происходит с Java 7, где вы можете теперь полагаться на собранные объекты Map.Entry, независимо от последующих итераций.

Лучший обходной путь, если вы собираетесь использовать запись после следующей итерации, - этоклонировать его через

new AbstractMap.SimpleImmutableEntry(entry)

и использовать вместо этого.

1 голос
/ 25 декабря 2011

Что вы подразумеваете под этим?

//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
1 голос
/ 25 декабря 2011

ОК, я приступил к изучению исходного кода - 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' в этом случае определяет это поведение для одной итерации.Если вы передаете его внутреннему классу, определите 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){}

Вот так.

0 голосов
/ 25 декабря 2011

Согласно Спецификации языка Java , расширенный цикл for при использовании итератора выглядит следующим образом:

for ( VariableModifiersopt Type Identifier: Expression) Statement

в точности соответствует:

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

(где I - это тип Expression.iterator()). В вашем случае VariableModifiersopt - это final. Как видно из эквивалентной формы, final применяется только к использованию переменной внутри Statement.

0 голосов
/ 25 декабря 2011

Я полагаю, что это потому, что ваша «запись» создается каждый раз, когда происходит цикл, так что это фактически новая переменная каждый раз. Если вы попытаетесь установить запись для чего-то еще внутри цикла, я полагаю, вы получите ошибку компилятора.

Редактировать, основываясь на отзывах: должно быть что-то, что вы нам не показываете.

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 Это объяснит вещи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...