Эффективность Java foreach

У меня примерно так:

Map<String, String> myMap = ...;

for(String key : myMap.keySet()) {
   System.out.println(key);
   System.out.println(myMap.get(key)); 
}

Так myMap.keySet() вызывается один раз в цикле foreach? Я думаю, что это так, но хочу вашего мнения.

Я хотел бы знать, влияет ли использование foreach таким образом (myMap.keySet()) на производительность или эквивалентно этому:

Set<String> keySet = myMap.keySet();
for (String key : keySet) {
   ...
}

person Flueras Bogdan    schedule 24.05.2009    source источник
comment
(Синтаксис расширенного цикла for немного сдвинут вперед.)   -  person Tom Hawtin - tackline    schedule 25.05.2009
comment
Не знаю, согласен ли я называть эту оптимизацию преждевременной. Разумно захотеть понять, что компилятор делает с вашим кодом. Мы также не знаем, в какой момент своего проекта (если он даже работает над проектом и не задает академических вопросов) он спрашивает об этом. Это могло быть в самом конце.   -  person James McMahon    schedule 31.07.2009


Ответы (6)


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

public void test() {
  Map<String, String> myMap = new HashMap<String, String>();

  for (String key : myMap.keySet()) {
    System.out.println(key);
    System.out.println(myMap.get(key));
  }

  Set<String> keySet = myMap.keySet();
  for (String key : keySet) {
    System.out.println(key);
    System.out.println(myMap.get(key));
  }
}

и когда я декомпилировал файл класса с помощью Jad, я получил:

public void test()
{
    Map myMap = new HashMap();
    String key;
    for(Iterator iterator = myMap.keySet().iterator(); iterator.hasNext(); System.out.println((String)myMap.get(key)))
    {
        key = (String)iterator.next();
        System.out.println(key);
    }

    Set keySet = myMap.keySet();
    String key;
    for(Iterator iterator1 = keySet.iterator(); iterator1.hasNext(); System.out.println((String)myMap.get(key)))
    {
        key = (String)iterator1.next();
        System.out.println(key);
    }
}

Вот и ваш ответ. Он вызывается один раз с любой формой цикла for.

person Eddie    schedule 24.05.2009
comment
Вау! это интересно ... Красиво. +1. - person Hari Krishna Ganji; 27.02.2013
comment
Извините за комментарий necro, но что-то здесь кажется странным. Почему оператор println находится в конце цикла for, а вызов next находится в теле? Похоже на очень странную установку. - person Carcigenicate; 05.07.2015
comment
@Carcigenicate Я согласен, это интригующая установка. Но логически это получается: к тому времени, когда будет выполнена конечная часть цикла for, метод next уже будет вызван на итераторе, так что все в порядке. Работа с необработанными итераторами всегда казалась мне немного странной, поскольку в большинстве случаев фактическая «итерация» происходит явно в начале цикла, а я использовал, чтобы это происходило (по крайней мере, концептуально) в конце предыдущего цикла. . - person Daniel Brady; 17.07.2015

Он вызывается только один раз. На самом деле для решения этой задачи используется итератор.

Кроме того, в вашем случае, я думаю, вам следует использовать

for (Map.Entry<String, String> entry : myMap.entrySet())
{
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

чтобы каждый раз не искать на карте.

person Valentin Rocher    schedule 24.05.2009
comment
Спасибо всем, что поделились своей мудростью! Желаю, чтобы мои сверстники были такими же! - person Flueras Bogdan; 25.05.2009
comment
Что делать, если myMap.entrySet () не возвращает постоянное значение (скажем, myMap обновляется в цикле, как добавление пары ключ-значение)? Вызывается только один раз? Разве это не привело бы к странным результатам? - person JavaTechnical; 21.07.2013
comment
@JavaTechnical: дело в том, что вы не должны изменять содержимое карты при обновлении, иначе вы получите ConcurrentModificationException. Если вам нужно изменить карту во время итерации, единственный безопасный способ сделать это - использовать итератор. - person Valentin Rocher; 23.07.2013

keySet() вызывается только один раз. «Расширенный цикл for» основан на интерфейсе Iterable, который он использует для получения Iterator, который затем используется для цикла. Невозможно перебрать Set каким-либо другим способом, поскольку нет индекса или чего-либо еще, с помощью которого можно было бы получить отдельные элементы.

Тем не менее, что вам действительно следует сделать, так это полностью отказаться от такого рода проблем с микрооптимизацией - если у вас когда-нибудь возникнут реальные проблемы с производительностью, вероятность того, что вы никогда не подумали бы об этом самостоятельно, составляет около 99%.

person Michael Borgwardt    schedule 24.05.2009
comment
что вам действительно следует сделать, так это полностью отказаться от такого рода проблем с микрооптимизацией. Его опасения вряд ли были связаны с микрооптимизацией в целом ... - person hhafez; 25.05.2009
comment
Вы можете создать вредоносный специальный случай, который приведет к огромным проблемам с производительностью практически для чего угодно, но это не меняет того факта, что это почти наверняка не было бы проблемой в любом случае - с одной стороны, наборы ключей кэшируются в любом Реализацию карты я когда-либо видел. - person Michael Borgwardt; 25.05.2009
comment
Микрооптимизация - я забочусь об этом! Они определенно имеют значение в сложных программах! - person JavaTechnical; 21.07.2013
comment
@JavaTechnical: Конечно, они имеют значение: они усложняют обслуживание сложных программ и, возможно, даже медленнее. Вы обязательно должны заботиться о них, избегая их. - person Michael Borgwardt; 21.07.2013

Ответ находится в спецификации языка Java, декомпилировать не нужно :) Это то, что мы можем прочитать о расширенный оператор for:

Расширенный оператор for имеет вид:

EnhancedForStatement:
        for ( VariableModifiersopt Type Identifier: Expression) Statement

Выражение должно иметь тип Iterable или тип массива (§10.1), иначе возникнет ошибка времени компиляции.

Объем локальной переменной, объявленной в части FormalParameter расширенного оператора for (§14.14), является содержащимся в нем оператором.

Значение расширенного оператора for дается переводом в базовый оператор for.

Если тип Expression является подтипом Iterable, тогда пусть I будет типом выражения Expression. iterator(). Расширенный оператор for эквивалентен базовому оператору for в форме:

for (I #i = Expression.iterator(); #i.hasNext(); ) {

        VariableModifiersopt Type Identifier = #i.next();
   Statement
}

Где #i - идентификатор, сгенерированный компилятором, который отличается от любых других идентификаторов (генерируемых компилятором или иным образом), которые находятся в области действия (§6.3) в точке, где возникает расширенный оператор for.

В противном случае выражение обязательно имеет тип массива, T[]. Пусть L1 ... Lm будет (возможно, пустой) последовательностью меток, непосредственно предшествующей расширенному оператору for. Тогда значение расширенного оператора for задается следующим основным оператором for:

T[] a = Expression;
L1: L2: ... Lm:
for (int i = 0; i < a.length; i++) {
        VariableModifiersopt Type Identifier = a[i];
        Statement
}

Где a и i - идентификаторы, сгенерированные компилятором, которые отличаются от любых других идентификаторов (сгенерированных компилятором или иным образом), которые находятся в области действия в точке, где возникает расширенный оператор for. .

В вашем случае myMap.keySet() возвращает подтип Iterable, поэтому ваш расширенный оператор for эквивалентен следующему базовому оператору for:

for (Iterator<String> iterator = myMap.keySet().iterator(); iterator.hasNext();) {
   String key = iterator.next();

   System.out.println(key);
   System.out.println(myMap.get(key)); 
}

Таким образом, myMap.keySet() вызывается только один раз.

person Pascal Thivent    schedule 14.01.2010

Да, в любом случае он вызывается только один раз

person James L    schedule 24.05.2009

Я считаю, что компилятор оптимизирован для запуска только один раз за запись цикла.

person Kris    schedule 24.05.2009