Объявление переменных внутри или вне цикла

Почему все работает нормально?

String str;
while (condition) {
    str = calculateStr();
    .....
}

Но этот считается опасным/неправильным:

while (condition) {
    String str = calculateStr();
    .....
}

Нужно ли объявлять переменные за пределами цикла?

Ответ 1

Область локальных переменных всегда должна быть минимальной.

В вашем примере я предполагаю, что str не используется вне цикла while, иначе вы не задали бы вопрос, потому что объявление его внутри цикла while не было бы вариантом, поскольку оно не было бы компиляции.

Итак, поскольку str не используется вне цикла, наименьшая возможная область для str находится внутри цикла while.

Таким образом, ответ категорически подчеркивает, что str должен быть объявлен внутри цикла while. Нет ifs, no ands, no buts.

Единственным случаем, когда это правило может быть нарушено, является то, что по какой-то причине жизненно важно, чтобы каждый тактовый цикл был вытеснен из кода, и в этом случае вам может потребоваться создать экземпляр чего-то во внешней области и повторно использовать вместо того, чтобы повторно создавать его на каждой итерации внутреннего пространства. Однако это не относится к вашему примеру из-за неизменности строк в java: новый экземпляр str всегда будет создан в начале вашего цикла, и его нужно будет выбросить в конец, так что здесь нет возможности оптимизировать.

РЕДАКТИРОВАТЬ: (вводя мой комментарий ниже в ответ)

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

Ответ 2

Я сравнил байтовый код этих двух (похожих) примеров:

Посмотрите на 1. Пример:

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

после javac Test.java, javap -c Test вы получите:

public class inside.Test extends java.lang.Object{
public inside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Посмотрим на 2. Пример:

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

после javac Test.java, javap -c Test вы получите:

public class outside.Test extends java.lang.Object{
public outside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Наблюдения показывают, что между этими двумя примерами нет разницы. Это результат спецификации JVM...

Но во имя лучшей практики кодирования рекомендуется объявлять переменную в минимально возможной области (в этом примере она находится внутри цикла, так как это единственное место, где используется переменная).

Ответ 3

Объявление объектов в наименьшей области улучшает читабельность .

Производительность не имеет значения для сегодняшних компиляторов (в этом случае)
С точки зрения обслуживания лучше выбрать 2nd.
Объявлять и инициализировать переменные в одном и том же месте, в самом узком объеме.

Как сказал Дональд Эрвин Кнут:

"Мы должны забыть о небольшой эффективности, скажем, около 97% времени: преждевременная оптимизация - это корень всех злых

i.e) ситуация, когда программист позволяет соображениям производительности влиять на дизайн части кода. Это может привести к тому, что дизайн не такой чистый, как он мог быть, или код, который является неправильным, потому что код сложный оптимизацией, а программист отвлекается на оптимизирующий.

Ответ 4

если вы хотите использовать str внешний looop; объявите его снаружи. в противном случае 2-я версия в порядке.

Ответ 5

Пожалуйста, перейдите к обновленному ответу...

Для тех, кто заботится о производительности, удалите System.out и ограничьте цикл 1 байтом. При использовании double (тест 1/2) и использовании String (3/4) истекшее время в миллисекундах приводится ниже для 64-битной Windows 7 Professional и JDK-1.7.0_21. Байт-коды (также приведены ниже для test1 и test2) не совпадают. Я был слишком ленив, чтобы проверить с изменчивым & относительно сложные объекты.

двойной

Тест1 занял: 2710 мсек

Тест2 занял: 2790 мсек

Строка (просто замените double на строку в тестах)

Тест3 занял 1200 мсек

Тест4 занял 3000 мсек

Компиляция и получение байт-кода

javac.exe LocalTest1.java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


Compiled from "LocalTest1.java"
public class LocalTest1 {
  public LocalTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


Compiled from "LocalTest2.java"
public class LocalTest2 {
  public LocalTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

ОБНОВЛЕННЫЙ ОТВЕТ

Нелегко сравнить производительность со всеми оптимизациями JVM. Однако это несколько возможно. Лучший тест и подробные результаты в Google Caliper

  1. Некоторые подробности в блоге:Должны ли вы объявить переменную внутри цикла или перед циклом?
  2. Репозиторий GitHub: https://github.com/gunduru/jvdt
  3. Результаты теста для двойного регистра и цикла 100M (и да всех деталей JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

DeclaredBefore 1,759.209 DeclaredInside 2,242.308

  • Объявлено до 1759.209 нс
  • Объявлено внутри 2 242 308 нс

Частичный тестовый код для двойной декларации

Это не идентично коду выше. Если вы просто кодируете фиктивный цикл, JVM пропускает его, так что по крайней мере вам нужно что-то присвоить и вернуть. Это также рекомендуется в документации по суппортам.

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

Резюме: Заявленный ранее указывает на лучшую производительность -really tiny- и это соответствует принципу наименьшего объема. JVM должна сделать это для вас

Ответ 6

Внутри, чем меньше область видимости переменной, тем лучше.

Ответ 7

Одним из решений этой проблемы может быть предоставление области переменных, инкапсулирующей цикл while:

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

Они будут автоматически отменены, когда внешний охват закончится.

Ответ 8

Если вам не нужно использовать str после цикла while (связанный с областью), тогда второе условие i.e.

  while(condition){
        String str = calculateStr();
        .....
    }

лучше, так как если вы определяете объект в стеке, только если condition - true. То есть используйте , если вам это нужно

Ответ 9

Я думаю, что лучшим ресурсом для ответа на ваш вопрос будет следующая публикация:

Разница между объявлением переменных до или в цикле?

По моему пониманию, эта вещь была бы зависимой от языка. IIRC Java оптимизирует это, поэтому нет никакой разницы, но JavaScript (например) будет выполнять все выделение памяти каждый раз в цикле. В Java, в частности, я думаю, что второй будет работать быстрее при выполнении профилирования.

Ответ 10

Объявление строковой строки за пределами цикла wile позволяет ссылаться на нее внутри и снаружи цикла while. Объявление String str внутри цикла while позволяет ссылаться на только внутри цикла while.

Ответ 11

Переменные должны быть объявлены как можно ближе к тому, где они используются.

Это упрощает преобразование RAII (Инициализация ресурсов).

Сохраняет ограниченность переменной. Это позволяет оптимизатору работать лучше.

Ответ 12

Как отметили многие люди,

String str;
while(condition){
    str = calculateStr();
    .....
}

НЕ лучше этого:

while(condition){
    String str = calculateStr();
    .....
}

Так что не объявляйте переменные за пределами их областей, если вы не используете их повторно...

Ответ 14

Объявление внутри цикла ограничивает область действия соответствующей переменной. Все зависит от требования проекта к сфере действия переменной.

Ответ 15

Воистину, вопрос, о котором говорилось выше, является проблемой программирования. Как вы хотите запрограммировать свой код? Где вам нужна "STR" для доступа? Нет смысла объявлять переменную, которая используется локально как глобальная переменная. Основы программирования, я считаю.

Ответ 16

Эти два примера приводят к тому же. Однако первый дает вам возможность использовать переменную str вне цикла while; второй - нет.

Ответ 17

Переменная str будет доступна и зарезервировать некоторое пространство в памяти даже после выполнения кода ниже.

 String str;
    while(condition){
        str = calculateStr();
        .....
    }

Переменная str не будет доступна, а также будет освобождена память, которая была выделена для переменной str в нижнем коде.

while(condition){
    String str = calculateStr();
    .....
}

Если бы мы следовали за вторым, это, безусловно, уменьшит нашу системную память и повысит производительность.

Ответ 18

Я думаю, что размер объекта имеет значение. В одном из моих проектов мы объявили и инициализировали большой двумерный массив, который заставлял приложение вызывать исключение из памяти. Мы переместили объявление из цикла и очистили массив в начале каждой итерации.

Ответ 19

Это на самом деле не отвечает на ваш вопрос ("почему это не работает?"), Но я хотел не согласиться с @mike-nakis и сказать, что блокобзор - плохая идея.

С точки зрения философии, циклы for/while являются частью функции, в которую они написаны, и не должны иметь своих собственных переменных. Функции и классы четко отделены друг от друга, поэтому определение области функций и классов - это хорошие идеи, но не определение области блоков. Блочная область видимости - это аномалия Java, хотя некоторые другие языки делают это тоже.

Более практическая причина не делать этого - то, что это не портативно. В JavaScript нет блочной области видимости, а в других языках - нет. Цитата https://www.oreilly.com/library/view/javascript-the-definitive/0596000480/ch04s03.html

Нет блока Область

Обратите внимание, что в отличие от C, C++ и Java, JavaScript не имеет области действия уровня блока. Все переменные, объявленные в функции, независимо от того, где они объявлены, определены во всей функции. В следующем коде переменные i, j и k имеют одинаковую область видимости: все три определены в теле функции. Это было бы не так, если бы код был написан на C, C++ или Java:

Поскольку вы ничего не получаете от локальной области видимости и поскольку он не переносимый, лучше вообще не использовать его.

Как правило, ваши подпрограммы должны быть достаточно короткими, чтобы найти место, где объявлена переменная, было легко. Если вам действительно нужна отдельная область видимости внутри функции, вам лучше вызывать другую функцию.

Ответ 20

Предупреждение для почти всех в этом вопросе: Вот пример кода, где внутри цикла он может быть в 200 раз медленнее на моем компьютере с Java 7 (и потребление памяти также немного отличается). Но речь идет о распределении, а не только области.

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

    public static void main(String[] args)
    {
        long start;

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

Заключение. В зависимости от размера локальной переменной разница может быть огромной даже при не очень больших переменных.

Просто сказать, что иногда, снаружи или внутри цикла имеет значение.

Ответ 21

У вас есть риск NullPointerException, если ваш метод calculateStr() возвращает значение null, а затем вы пытаетесь вызвать метод на странице.

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