2009-01-06 4 views
88

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

И это также приводит к проблемам, как объяснено в this comment to a blog post:

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

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

+1

Тот же вопрос: [http://stackoverflow.com/questions/268814/uninitialized-variables-and-members-in-java](http://stackoverflow.com/questions/268814/uninitialized-variables-and- members-in-java) –

+1

Извините, что ... этот вопрос не появился, когда я вводил вопрос. Однако, я думаю, есть разница между двумя вопросами ... Я хочу знать, почему дизайнеры Java * спроектировал * это так, в то время как вопрос, на который вы указали, не спрашивает, что ... –

+1

, потому что инициализация уже не была забавой. – Bastardo

ответ

3

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

+1

Возможно, компилятор может определить, всегда ли вы присваиваете переменную, прежде чем что-либо делать с ней, и подавить автоматическое присвоение значения по умолчанию в таком случае. Если компилятор не может определить, произойдет ли назначение до доступа, он будет генерировать назначение по умолчанию. –

+2

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

+1

Компилятор мог сделать это и в любом случае. :) Лично я бы предпочел, чтобы компилятор рассматривал неинициализированную переменную как ошибку. Это означает, что я мог где-то совершить ошибку. –

54

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

10

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

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

22

The "problem" you link to, кажется, описывая эту ситуацию: жалоба

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, которые автоматически соединяются с предыдущим исключением? Я не помню. Тем не менее, у вас было бы лишнее исключение на пути реального.)

+2

Почему не просто if (so! = Null) ... в блоке finally? – izb

+0

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

+6

Я бы поставил только SomeObject так = null перед тем, как попробовать, и поставьте нулевую проверку на предложение finally. Таким образом не будет предупреждений компилятора. –

0

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

2

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

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

12

Кроме того, в приведенном ниже примере, исключение может быть брошено внутри конструкции SomeObject, в этом случае «так» переменная будет пустой и призыв к очищающей будет бросать 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 
    } 
} 
+0

Это просто бесполезно. –

+12

Что бы вы предпочли? –

+2

Yup, уродливый. Да, это то, что я тоже делаю. –

0

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

+2

Плохое ошибочное ... все непримитивы Java хранятся в куче независимо от того, когда и как они построены. – gshauger

+0

Перед Java 7 переменные экземпляра хранятся в куче, а локальные переменные находятся в стеке.Однако любой объект, который ссылается на локальную переменную, будет найден в куче. Начиная с Java 7, «компилятор сервера Java Hotspot» может выполнять «анализ отклика» и решает выделить некоторые объекты в стеке вместо кучи. – mamills

3

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

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

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

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

8

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

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

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

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

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

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

+1

Да, и есть другие, где более или менее необходимо инициализировать нулевое значение из-за того, как происходит поток кода - я бы не сказал «никогда» не обновил ответ, чтобы отразить это. –

-1

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

-2

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

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

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

+0

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

0

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

Смежные вопросы