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



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



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


но это, как говорят, опасно / неправильно:



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


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

1926   20  

20 ответов:

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

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

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

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

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

EDIT: (вводя мой комментарий ниже в ответ)

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

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

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

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

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

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

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

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

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

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

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

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

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

они будут автоматически де-ссылки, когда внешняя область заканчивается.

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

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

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

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

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

двойной

Тест1 занял: 2710 МС

Test2 взял: 2790 МС

String (просто замените double на string в тестах)

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
}

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

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

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

DeclaredBefore 1,759.209 DeclaredInside 2,242.308

  • объявлено ранее 1,759. 209 НС
  • DeclaredInside 2,242. 308 ns

частичный тестовый код для двойного объявления

это не совпадает с выше код. Если вы просто кодируете фиктивный цикл, 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;
}

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

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

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

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

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

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

Это делает RAII (Сбор Ресурсов Является Инициализацией) легче.

Он держит объем переменной плотно. Это позволяет оптимизатору работать лучше.

согласно Руководству по разработке Google Android, переменная область должна быть ограничена. Пожалуйста, проверьте эту ссылку:

Ограничить Область Действия Переменной

как указывали многие люди,

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

и не лучше, чем этот:

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

поэтому не объявляйте переменные вне их областей, если вы не используете его повторно...

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

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

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

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

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

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

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

эти два примера приводят к одному и тому же. Тем не менее, первый предоставляет вам с помощью str переменная вне цикла while; второй-нет.

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

предупреждение почти для всех в этом вопросе: Вот пример кода, где внутри цикла он может быть легко в 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);
    }
}

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

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

у вас есть риск NullPointerException Если calculateStr() возвращает null а затем вы пытаетесь вызвать метод на str.

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

Comments

    Ничего не найдено.