Объявление переменных внутри или вне цикла - PullRequest
212 голосов
/ 10 января 2012

Почему следующее работает нормально?

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

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

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

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

Ответы [ 20 ]

282 голосов
/ 16 января 2012

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

Давайте посмотрим на 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 ...

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

270 голосов
/ 10 января 2012

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

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

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

Итак, ответ категорически , что str абсолютно необходимо объявить в цикле while. Нет, если, нет и нет, но нет.

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

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

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

23 голосов
/ 22 января 2012

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

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

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

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

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

12 голосов
/ 10 января 2012

, если вы хотите использовать str также вне цикла; объявить это снаружи. в противном случае вторая версия в порядке.

8 голосов
/ 28 июня 2013

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

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

double

Test1 заняло: 2710 мсек

Test2 заняло: 2790 мсек

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

Test3 заняло: 1200 мсек

Test4 заняло: 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 пропускает его, так что по крайней мере вам нужно что-то присвоить и вернуть.Это также рекомендуется в документации по Caliper.

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

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

7 голосов
/ 10 января 2012

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

7 голосов
/ 18 сентября 2015

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

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

Они будут автоматически разыменовываться по окончании внешнего объема.

6 голосов
/ 10 января 2012

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

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

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

4 голосов
/ 10 сентября 2015

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

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

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

3 голосов
/ 10 января 2012

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

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