ローカル変数がJavaで初期化されないのはなぜですか?


103

Javaの設計者がローカル変数にデフォルト値を与えるべきではないと感じた理由はありましたか?真剣に、インスタンス変数にデフォルト値を与えることができるなら、なぜローカル変数に対して同じことをできないのでしょうか?

また、このブログ投稿へのコメントで説明されているように、問題にもつながります。

さて、このルールは、finallyブロックでリソースを閉じようとするときに最もイライラします。try内でリソースをインスタンス化し、finally内でそれを閉じようとすると、このエラーが発生します。インスタンス化を試行の外に移動すると、試行内にある必要があることを示す別のエラーが表示されます。

とてもイライラします。


1
同じ質問:http
Yuval Adam

1
それについて申し訳ありません...私が質問を入力しているときにこの質問はポップアップしませんでした...しかし、私は2つの質問の間に違いがあると思います... Javaの設計者がなぜこのようにそれを設計したのか知りたいのですが、あなたが指摘した質問はそれを求めていません...
Shivasubramanian A

また、この関連C#の質問を参照してください:stackoverflow.com/questions/1542824/...
レドワルド

単に-コンパイラが初期化されていないローカル変数を追跡するのは簡単だからです。他の変数と同じことができれば、そうなります。コンパイラは単にあなたを助けようとしています。
rustyx

回答:


62

ローカル変数は、主に何らかの計算を行うために宣言されます。したがって、変数の値を設定するというプログラマの決定であり、デフォルト値をとるべきではありません。プログラマが誤ってローカル変数を初期化せず、デフォルト値を使用した場合、出力は予期しない値になる可能性があります。したがって、ローカル変数の場合、コンパイラーは、未定義の値の使用を回避するために、変数にアクセスする前に、プログラマーに何らかの値で初期化するように求めます。


23

あなたがリンクしている「問題」は、この状況を説明しているようです:

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();
}

コメンターはその解決策に不満を抱いています。なぜなら、コンパイラーはコードを「試してみる必要がある」と言っているからです。つまり、一部のコードでは、処理されなくなった例外が発生する可能性があります。よく分かりません。私のコードのどちらのバージョンも例外を処理しないため、最初のバージョンで例外に関連するものは2番目のバージョンでも同じように機能するはずです。

とにかく、この2番目のバージョンのコードは、正しい記述方法です。最初のバージョンでは、コンパイラのエラーメッセージは正しかった。so変数は初期化される可能性があります。特に、SomeObjectコンストラクターが失敗した場合はso初期化されないため、を呼び出そうとするとエラーになりますso.CleanUp。セクションが確定するリソースを取得したは、必ずtryセクション入力してくださいfinally

初期化後のtry- finallyブロックsoは、インスタンスを保護するためだけにありSomeObject、他に何が起こってもインスタンスが確実にクリーンアップされるようにします。ある場合は、他の必要性は、実行すること、物事は、彼らがいないかどうかに関係しているSomeObjectインスタンスは、プロパティが割り当てられた後、彼らはに行くべき別の try - finallyおそらく、ブロックIが示されてきたものを包むものを。

変数を使用する前に手動で割り当てる必要があることは、実際の問題にはつながりません。それはマイナーな面倒につながるだけですが、あなたのコードはそれのために優れています。あなたはより限定されたスコープを持つ変数、および必要がありますtry- finallyあまりを保護しようとしないブロックを。

ローカル変数にデフォルト値がある場合so、最初の例ではになりますnull。それは本当に何も解決しなかったでしょう。finallyブロック内でコンパイル時エラーが発生する代わりに、コードの「Do some work here」セクションで発生する可能性のある他の例外を非表示NullPointerExceptionにする可能性がある潜んでいます。(または、セクション内の例外が自動的に前の例外にチェーンされますか?覚えていません。それでも、実際の例外とは別の例外があります。)finally


2
なぜ、finallyブロックにif(so!= null)...がないのですか?
izb 2009年

それでもコンパイラの警告/エラーが発生します-チェックした場合、コンパイラがそれを理解しているとは思いません(ただし、テストしていないので、メモリ外で実行するだけです)。
ちー

6
SomeObject so = nullを配置する前にnullを配置して、finally句にnullチェックを配置します。この方法では、コンパイラの警告は発生しません。
JuhaSyrjälä、2009年

なぜ物事を複雑にするのですか?この方法でtry-finallyブロックを作成すると、変数に有効な値があることがわかります。nullチェックは不要です。
ロブ・ケネディ

1
ロブ、あなたの例の「新しいSomeObject()」は単純であり、例外はそこで生成されるべきではありませんが、呼び出しが例外を生成できる場合は、処理できるようにtryブロック内で発生させるほうが良いでしょう。
Sarel Botha、

12

さらに、以下の例では、SomeObject構造の内部で例外がスローされている可能性があります。その場合、「so」変数はnullになり、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
  }
}

12
何をしますか?
電気モンク

2
うん、醜い。うん、それも私がやっていることです。
SMBiggs 2013年

@ElectricMonk getContents(..)メソッドでここに示したものと、ここに示したもののどちらが優れていると思いますか。javapractices.com/topic/TopicAction.do? Id
Atom

11

最終的なインスタンス/メンバー変数はデフォルトでは初期化されないことに注意してください。これらは最終的なものであり、後でプログラムで変更することはできません。これが、Javaがデフォルト値を提供せず、プログラマに初期化を強制する理由です。

一方、非最終メンバー変数は後で変更できます。したがって、これらは後で変更できるため、コンパイラーはそれらを未初期化のままにしないでください。ローカル変数に関しては、ローカル変数のスコープははるかに狭いです。コンパイラーは、いつ使用されるかを認識しています。したがって、プログラマに変数の初期化を強制することは理にかなっています。


9

あなたの質問に対する実際の答えは、スタック変数に数値を追加するだけでメソッド変数がインスタンス化されるためです。それらをゼロにすることは追加のステップになるでしょう。クラス変数の場合、それらはヒープ上の初期化されたメモリに入れられます。

追加の手順を実行しませんか?一歩下がってください-この場合の「警告」が非常に良いことであると誰も述べていません。

最初のパス(最初にコーディングするとき)では、変数をゼロまたはnullに初期化しないでください。これを実際の値に割り当てるか、まったく割り当てないでください。そうしないと、Javaが実際に失敗したときに通知することができます。エレクトリックモンクの答えを例に取ってみましょう。最初のケースでは、SomeObjectのコンストラクターが例外をスローしたためにtry()が失敗した場合、最終的にNPEが発生することを通知するので、驚くほど便利です。コンストラクターが例外をスローできない場合、それを試してはいけません。

この警告はすべてのパスをチェックし、あるパスで変数を使用した場合、それに至るすべてのパスで変数を初期化する必要があることを確認するので、愚かなことから私を救った素晴らしいマルチパスの悪いプログラマチェッカーです。正しいことであると判断するまで、変数を明示的に初期化することはありません。

その上で、「int size」ではなく「int size = 0」と明示的に言って、次のプログラマーにゼロにするつもりであると理解させてもらえませんか?

反対に、初期化されていないすべての変数をコンパイラーに0に初期化させる正当な理由が1つもありません。


1
はい、そしてコードの流れ方のために、多かれ少なかれnullに初期化する必要がある他のものがあります-これを反映するために「決して」答えを更新したとは言ってはいけません。
ビルK

4

主な目的はC / C ++との類似性を維持することだったと思います。ただし、コンパイラーは、初期化されていない変数の使用を検出して警告します。これにより、問題が最小限に抑えられます。パフォーマンスの観点からは、次のステートメントで変数の値を上書きしても、コンパイラーが代入ステートメントを記述する必要がないため、初期化されていない変数を宣言する方が少し高速です。


1
おそらく、コンパイラーは、変数に何かを行う前に常に変数に割り当てるかどうかを決定し、そのような場合には自動デフォルト値の割り当てを抑制できます。コンパイラーがアクセス前に割り当てが発生するかどうかを判別できない場合は、デフォルトの割り当てが生成されます。
グレッグヒューギル

2
はい、しかし、プログラマが変数を誤って初期化せずに残したかどうかをプログラマに知らせると主張する人がいるかもしれません。
Mehrdad Afshari、

1
コンパイラはどちらの場合でもそれを行うことができます。:)個人的には、コンパイラが初期化されていない変数をエラーとして扱うことを好みます。それは私がどこかで間違いをしたかもしれないことを意味します。
グレッグヒューギル

私はJavaの男ではありませんが、C#の扱い方が好きです。違いはその場合です。コンパイラは警告を発行する必要があり、正しいプログラムに対して数百の警告が表示される可能性があります;)
Mehrdad Afshari

メンバー変数についても警告しますか?
Adeel Ansari、

4

(質問のすぐ後に新しい回答を投稿するのは奇妙に思えるかもしれませんが、重複が出てきました。)

私にとっての理由はこれに帰着します。ローカル変数の目的はインスタンス変数の目的とは異なります。ローカル変数は、計算の一部として使用されます。インスタンス変数は状態を含むためのものです。値を割り当てずにローカル変数を使用する場合、それはほぼ間違いなく論理エラーです。

とは言っても、インスタンス変数を常に明示的に初期化する必要があるため、完全に遅れることができました。エラーは、結果が初期化されていないインスタンス変数を許可するすべてのコンストラクターで発生します(たとえば、宣言時に初期化されておらず、コンストラクター内にもない)。しかし、それはゴスリングなどの決定ではありません。その他、90年代前半に取り入れたので、ここにあります。(そして私は彼らが間違った電話をしたと言っているのではありません。)

しかし、ローカル変数のデフォルト設定に遅れをとることできませんでした。はい、コンパイラに依存してロジックを再確認するべきではありません。また、そうではありませんが、コンパイラが1つを見つけた場合は、便利です。:-)


「とは言っても、インスタンス変数を常に明示的に初期化することを要求することは完全に遅れることができます...」これは、FWIWがTypeScriptで取った方向です。
TJクラウダー

3

初期化はコンパイラによって追跡できるため、変数を初期化しない方が効率的です。ローカル変数の場合は、安全に初期化できます。

変数を初期化する必要がある場合は、常に自分で行うことができるため、問題ありません。


3

ローカル変数の背後にある考え方は、それらが必要とされる限られたスコープ内にのみ存在するということです。そのため、値について、または少なくとも、その値がどこから来ているのかについて不確実性が生じる理由はほとんどないはずです。ローカル変数にデフォルト値があることから生じる多くのエラーを想像できます。

たとえば、次の単純なコードを考えてみましょう ...(明示的に初期化されていない場合、ローカル変数には指定されたデフォルト値が割り当てられていることをデモンストレーションの目的で想定します

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);

すべてのことを言い終えたらコンパイラーがletterGradeデフォルト値 '\ 0'を割り当てた仮定すると、このコードは記述されたとおりに正しく機能します。しかし、elseステートメントを忘れた場合はどうなりますか?

コードをテスト実行すると、次のような結果になる可能性があります

Enter grade
43
Your grade is

この結果は、予想通りでしたが、確かにコーダーの意図ではありませんでした。実際、おそらく大多数のケース(または少なくともそのかなりの数)では、デフォルト値は望ましい値ではないため、ほとんどの場合、デフォルト値はエラーになります。忘れによって引き起こさデバッグ悲しみので、それは、それを使用する前に、ローカル変数に初期値を割り当てるためにコーダを強制するために、より理にかなっている= 1for(int i = 1; i < 10; i++)遠くには、する必要がないで利便性を上回る= 0中をfor(int i; i < 10; i++)

たとえば、オブジェクトがコンストラクターでチェックされた例外をスローする場合、try-catch-finallyブロックが少し乱雑になる可能性があることは事実です(ただし、実際にはcatch-22ではありません)。理由または別の方法では、最終的にブロックの最後でこのオブジェクトに対して何かを行う必要あります。これの完全な例は、閉じなければならないリソースを扱う場合です。

過去のこれを処理する1つの方法はそうかもしれません...

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");
}

つまり、(名前が示すように)これはリソースでのみ機能します。

そして、前者の例は少し不愉快ですが、これはおそらくローカル変数とそれらがどのように実装されているかについて話すよりも、try-catch-finallyやこれらのクラスが実装されている方法について多くを語っています。

フィールドがデフォルト値に初期化されることは事実ですが、これは少し異なります。たとえばと言うと、int[] arr = new int[10];この配列を初期化するとすぐに、オブジェクトはメモリ内の指定された場所に存在します。しばらくの間、デフォルト値がないと仮定しましょう。代わりに、初期値は、その時点でそのメモリ位置にある1と0の連続です。これにより、多くの場合、非決定的な動作が発生する可能性があります。

あるとしましょう...

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の各要素は文字列を指す必要があります(またはnullです)。ただし、初期値がこのメモリ位置で偶然発生する一連の0と1である場合、毎回同じ結果が得られる保証はないだけでなく、オブジェクトs [0]がポイントする保証もありません。 (それは意味のあるものを指すと仮定した場合)にも、ある文字列(おそらくそれはウサギだ、:P)!型に対するこの懸念の欠如は、JavaをJavaにするほとんどすべてのものに直面して飛行します。したがって、ローカル変数のデフォルト値を持つことはせいぜいオプションであると見なすことができますが、インスタンス変数のデフォルト値を持つことは必要性に近いです。


1

私が間違っていない場合、別の理由が考えられます

メンバー変数のデフォルト値を与えることはクラスローディングの一部です

クラスローディングとは、Javaでの実行時のことです。つまり、オブジェクトを作成すると、クラスローディングでクラスがロードされます。デフォルト値で初期化されるのはメンバー変数のみですJVMは、ローカル変数にデフォルト値を与えるのに時間がかかりません。メソッド呼び出しは条件付きである可能性があるため、呼び出されます。デフォルトを使用しない場合は、デフォルト値を指定してパフォーマンスを低下させるのに時間がかかるのはなぜですか。


0

Eclipseは初期化されていない変数の警告を表示することさえあるので、とにかくそれは非常に明白になります。個人的にはこれがデフォルトの動作であることは良いことだと思います。それ以外の場合、アプリケーションは予期しない値を使用する可能性があり、コンパイラーがエラーをスローする代わりに何も実行せず(おそらく警告を表示し)、その後スクラッチします特定のものが正常に動作しない理由についてのあなたの頭。


0

ローカル変数はスタックに格納されますが、インスタンス変数はヒープに格納されるため、ヒープで発生するデフォルト値ではなく、スタック上の以前の値が読み取られる可能性があります。そのため、jvmでは、初期化せずにローカル変数を使用できません。


2
完全に間違っている...すべてのJava非プリミティブは、いつどのように構築されるかに関係なく、ヒープに格納されます
gshauger

Java 7より前のバージョンでは、インスタンス変数はヒープに格納され、ローカル変数はスタックにあります。ただし、ローカル変数が参照するオブジェクトはヒープ内にあります。Java 7以降、「Java Hotspot Server Compiler」は「エスケープ分析」を実行し、ヒープではなくスタックにオブジェクトを割り当てることを決定する場合があります。
mamills 2013

0

インスタンス変数にはデフォルト値がありますが、ローカル変数にはデフォルト値がありません。ローカル変数は基本的にメソッド/動作内にあるため、その主な目的は、いくつかの演算または計算を行うことです。したがって、ローカル変数にデフォルト値を設定することはお勧めできません。そうしないと、予期しない回答の理由を確認するのが非常に難しく、時間がかかります。


-1

答えは、インスタンス変数はクラスコンストラクターまたは任意のクラスメソッドで初期化できるということですが、ローカル変数の場合は、クラスに永久に残るメソッドで何でも定義した後です。


-2

次の2つの理由が考えられます

  1. ほとんどの答えはローカル変数の初期化の制約を課すことで述べたように、ローカル変数にはプログラマーが望む値が割り当てられ、期待される結果が計算されることが保証されます。
  2. ローカル変数(同じ名前)を宣言することにより、インスタンス変数を非表示にすることができます。期待される動作を保証するために、ローカル変数は強制的に値を初期化します。(ただし、これは完全に避けてください)

フィールドは上書きできません。せいぜい、非表示にできますが、非表示にすると初期化のチェックがどのように妨げられるのかわかりません。
メリトン2014

右非表示。インスタンスと同じ名前のローカル変数を作成する場合、この制約により、ローカル変数は仮に選択された値(インスタンス変数の値以外)で初期化されます
Mitra
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.