Почему локальные переменные не инициализируются в Java?

Была ли причина, по которой разработчики Java чувствовали, что локальным переменным не следует присваивать значение по умолчанию? Серьезно, если переменным экземпляра можно присвоить значение по умолчанию, то почему мы не можем сделать то же самое для локальных переменных?

И это также приводит к проблемам, описанным в этот комментарий к сообщению в блоге:

Ну, это правило наиболее расстраивает при попытке закрыть ресурс в блоке finally. Если я создам ресурс внутри попытки, но попытаюсь закрыть его в конце, я получаю эту ошибку. Если я перемещаю экземпляр вне try, я получаю еще одну ошибку, заявляя, что она должна быть в попытке.

Очень расстраивает.

Ответ 1

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

Ответ 2

"Проблема" , которую вы ссылаетесь на, кажется, описывает эту ситуацию:

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Жалоба комментатора заключается в том, что компилятор не работает в строке в разделе finally, утверждая, что so может быть неинициализирован. В комментарии затем упоминается другой способ написания кода, возможно, что-то вроде этого:

// Do some work here ...
SomeObject so = new SomeObject();
try {
  so.DoUsefulThings();
} finally {
  so.CleanUp();
}

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

В любом случае, эта вторая версия кода - правильный способ ее записи. В первой версии сообщение об ошибке компилятора было правильным. Переменная so может быть неинициализирована. В частности, если конструктор SomeObject терпит неудачу, so не будет инициализирован, и поэтому будет попытка попытаться вызвать so.CleanUp. Всегда вводите раздел try после того, как вы приобрели ресурс, который завершает раздел finally.

Блок try - finally после инициализации so существует только для защиты экземпляра SomeObject, чтобы убедиться, что он очищается независимо от того, что еще происходит. Если есть другие вещи, которые необходимо выполнить, но они не связаны с тем, был ли экземпляр SomeObject выделен как свойство, то он должен пойти в другом блоке try - finally, возможно, в том, показанный.

Обязательные переменные, назначаемые вручную перед использованием, не приводят к реальным проблемам. Это только приводит к незначительным неприятностям, но ваш код будет лучше для него. У вас будут переменные с более ограниченной областью, а try - finally блоки, которые не пытаются защитить слишком много.

Если локальные переменные имели значения по умолчанию, то so в первом примере было бы null. Это ничего бы не решило. Вместо того, чтобы получить ошибку времени компиляции в блоке finally, у вас будет скрытый NullPointerException, который может скрыть любое другое исключение в разделе "Сделайте некоторые работы здесь". (Или исключения в разделах finally автоматически соединяются с предыдущим исключением? Я не помню. Тем не менее, у вас было бы лишнее исключение на пути реального.)

Ответ 3

Кроме того, в приведенном ниже примере исключение может быть выбрано внутри конструкции SomeObject, и в этом случае переменная "so" будет пустой, а вызов CleanUp вызовет исключение NullPointerException

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Что я имею в виду:

SomeObject so = null;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  if (so != null) {
     so.CleanUp(); // safe
  }
}

Ответ 4

Обратите внимание, что конечные переменные instance/member не инициализируются по умолчанию. Потому что они являются окончательными и впоследствии не могут быть изменены в программе. Это причина, по которой Java не дает никакого значения по умолчанию для них и заставляет программиста инициализировать его.

С другой стороны, не конечные переменные-члены могут быть изменены позже. Следовательно, компилятор не позволяет им оставаться неинициализированными точно, потому что они могут быть изменены позже. Что касается локальных переменных, то объем локальных переменных значительно уже. Компилятор знает, когда он будет использоваться. Следовательно, принуждение программиста к инициализации переменной имеет смысл.

Ответ 5

Фактический ответ на ваш вопрос заключается в том, что переменные метода создаются путем простого добавления числа к указателю стека. Ноль их будет дополнительным шагом. Для переменных класса они помещаются в инициализированную память в куче.

Почему бы не сделать дополнительный шаг? Сделайте шаг назад - никто не упомянул, что "предупреждение" в этом случае - очень хорошая вещь.

Вы никогда не должны инициализировать свою переменную нулевым или нулевым на первом проходе (когда вы сначала его кодируете). Либо присвойте его фактическому значению, либо не назначьте его вообще, потому что, если вы этого не сделаете, java может сказать вам, когда вы действительно испортите. Возьмите Electric Monk в качестве отличного примера. В первом случае, это действительно удивительно полезно, что он говорит вам, что если try() не сработает, потому что SomeObject-конструктор создал исключение, то в конечном итоге вы получите NPE. Если конструктор не может генерировать исключение, он не должен быть в try.

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

Кроме того, не лучше ли явно указывать "int size = 0", а не "int size", и заставить следующего программиста понять, что вы намереваетесь его равным нулю?

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

Ответ 6

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

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

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

Однако я не мог отстать от локальных переменных, не соответствующих стандарту. Да, мы не должны полагаться на компиляторы, чтобы проверить нашу логику, а другая нет, но она по-прежнему удобна, когда компилятор ловит ее.: -)

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Например, рассмотрим следующий простой код... (NB допустим для демонстрационных целей, что локальным переменным присваивается значение по умолчанию, как указано, если явно не инициализировано)

System.out.println("Enter grade");
int grade = new Scanner(System.in).nextInt(); //I won't bother with exception handling here, to cut down on lines.
char letterGrade; //let us assume the default value for a char is '\0'
if (grade >= 90)
    letterGrade = 'A';
else if (grade >= 80)
    letterGrade = 'B';
else if (grade >= 70)
    letterGrade = 'C';
else if (grade >= 60)
    letterGrade = 'D';
else
    letterGrade = 'F';
System.out.println("Your grade is " + letterGrade);

Когда все сказано и сделано, предполагая, что компилятор присвоил значение по умолчанию '\ 0' для letterGrade, этот код, как написано, будет работать правильно. Однако, что, если мы забыли другое выражение?

Пробный запуск нашего кода может привести к следующему

Enter grade
43
Your grade is

Этот результат, который, как и следовало ожидать, наверняка не был целью кодера. Действительно, вероятно, в подавляющем большинстве случаев (или, по крайней мере, в их значительном числе) значение по умолчанию не будет желательным значением, поэтому в подавляющем большинстве случаев значение по умолчанию приведет к ошибке. Имеет смысл заставить кодера назначать начальное значение локальной переменной перед ее использованием, поскольку печатание отладки, вызванное забыванием = 1 in for(int я = 1; я < 10; i++) намного for(int я = 1; я < 10; i++) удобство в том, чтобы не включать значение = 0 in for(int i; я < 10; i++).

Это правда, что блоки try-catch-finally могут немного запутаться (но на самом деле это не catch-22, как кажется, кажется, цитата), когда, например, объект выдает проверенное исключение в своем конструкторе, но для одного причина или другое, что-то должно быть сделано с этим объектом в конце блока в конце. Прекрасным примером этого является обращение к ресурсам, которые должны быть закрыты.

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

Scanner s = null; //declared and initialized to null outside the block. This gives us the needed scope, and an initial value.
try {
    s = new Scanner(new FileInputStream(new File("filename.txt")));
    int someInt = s.nextInt();
} catch (InputMismatchException e) {
    System.out.println("Some error message");
} catch (IOException e) {
    System.out.println("different error message"); 
} finally {
    if (s != null) //in case exception during initialization prevents assignment of new non-null value to s.
        s.close();
}

Однако, с Java 7, этот окончательный блок больше не нужен, используя try-with-resources, например.

try (Scanner s = new Scanner(new FileInputStream(new File("filename.txt")))) {
...
...
} catch(IOException e) {
    System.out.println("different error message");
}

Тем не менее (как следует из названия) это работает только с ресурсами.

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

Верно, что поля инициализируются значением по умолчанию, но это немного отличается. Когда вы говорите, например, int[] arr = new int[10]; , как только вы инициализировали этот массив, объект существует в памяти в определенном месте. Предположим на мгновение, что нет значений по умолчанию, но вместо этого начальное значение - это то, что на данный момент в ячейке памяти находится серия из 1s и 0s. В ряде случаев это может привести к детерминированному поведению.

Предположим, что у нас есть...

int[] arr = new int[10];
if(arr[0] == 0)
    System.out.println("Same.");
else
    System.out.println("Not same.");

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

String[] s = new String[5];

Согласно определению, каждый элемент s должен указывать на String (или null). Однако, если начальное значение представляет собой то, что происходит в серии 0s и 1s в этой ячейке памяти, не только нет никакой гарантии, что вы получите одинаковые результаты каждый раз, но также нет гарантии, что объект s [0] указывает (если предположить, что это указывает на что-либо значимое), даже является строкой (возможно, это кролик, p)! Это отсутствие заботы о типе будет лежать перед лицом почти всего, что делает Java Java. Таким образом, хотя значения по умолчанию для локальных переменных можно рассматривать как необязательные в лучшем случае, значения по умолчанию для переменных экземпляра ближе к необходимости.

Ответ 10

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

Ответ 11

Локальные переменные хранятся в стеке, но переменные экземпляра хранятся в куче, поэтому есть вероятность, что предыдущее значение в стеке будет считано вместо значения по умолчанию, которое происходит в куче. По этой причине jvm не позволяет использовать локальную переменную без ее инициализации.

Ответ 12

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

Ответ 13

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

Ответ 14

Я мог бы думать о следующих двух причинах

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