110

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

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

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

とてもイライラします。

4

17 に答える 17

65

ローカル変数は、主に何らかの計算を行うために宣言されます。したがって、変数の値を設定するのはプログラマーの決定であり、デフォルト値を取るべきではありません。

プログラマーが誤ってローカル変数を初期化せずにデフォルト値を取った場合、出力は予期しない値になる可能性があります。したがって、ローカル変数の場合、コンパイラーは、未定義の値の使用を避けるために、変数にアクセスする前に、何らかの値で初期化するようプログラマーに要求します。

于 2009-01-06T07:18:08.323 に答える
24

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

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ブロックは、インスタンスを保護するためだけに存在し、他に何が起こっても確実にクリーンアップされるようにします。に実行する必要があるが、それらがインスタンスが割り当てられたプロパティであるかどうかに関係がない場合は、別のブロックに移動する必要があります。おそらく、私が示したものをラップするブロックです。soSomeObjectSomeObject tryfinally

変数を使用する前に手動で割り当てる必要があっても、実際の問題は発生しません。マイナーな問題が発生するだけですが、コードはより適切になります。スコープがより限定された変数と、tryあまりfinally保護しようとしないブロックがあります。

ローカル変数にデフォルト値がある場合so、最初の例ではnull. それは本当に何も解決しなかったでしょう。finallyブロックでコンパイル時エラーを取得する代わりに、コードの「ここで何らかの作業を行う」セクションで発生する可能性のある他の例外を隠すNullPointerException可能性のあるエラーがそこに潜んでいます。(または、セクション内の例外は自動的に前の例外に連鎖しますか?私は覚えていません。それでも、実際の例外の途中で余分な例外が発生します。)finally

于 2009-01-06T08:00:25.430 に答える
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
  }
}
于 2009-01-06T09:58:43.257 に答える
12

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

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

于 2009-01-06T07:31:26.037 に答える
10

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

余分な一歩を踏み出してみませんか?一歩下がってください。この場合の「警告」が非常に良いことであるとは誰も言及していません。

最初のパス (最初にコーディングするとき) で変数をゼロまたは null に初期化しないでください。それを実際の値に割り当てるか、まったく割り当てないでください。そうしないと、いつ本当に失敗したかをJavaが教えてくれるからです。Electric Monk の回答を良い例として挙げてください。最初のケースでは、SomeObject のコンストラクターが例外をスローしたために try() が失敗した場合、finally でNPEが発生することを通知することは、実際には驚くほど便利です。コンストラクターが例外をスローできない場合は、try に含めるべきではありません。

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

その上で、「int size」ではなく「int size=0」と明示して、次のプログラマーに意図的にゼロにすることを理解させたほうがよいのではないでしょうか。

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

于 2011-05-20T23:11:24.883 に答える
4

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

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

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

于 2010-03-25T08:24:13.447 に答える
4

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

于 2009-01-06T07:12:15.653 に答える
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);

結局のところ、コンパイラがデフォルト値の '\0' を letterGradeに割り当てたと仮定すると、書かれているこのコードは適切に機能します。しかし、else ステートメントを忘れたらどうなるでしょうか。

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

Enter grade
43
Your grade is

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

try-catch-finally ブロックが少し乱雑になる可能性があることは事実です (ただし、引用が示唆しているように、実際には catch-22 ではありません)。何らかの理由で、finally のブロックの最後でこのオブジェクトに対して何かを行う必要があります。これの完璧な例は、閉じる必要があるリソースを扱う場合です。

過去にこれを処理する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 などを使用すると、この finally ブロックは不要になりました。

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

于 2018-09-12T02:49:20.270 に答える
0

The local variables are stored on a stack, but instance variables are stored on the heap, so there are some chances that a previous value on the stack will be read instead of a default value as happens in the heap.

For that reason the JVM doesn't allow to use a local variable without initializing it.

于 2009-01-28T16:50:53.543 に答える
0

Eclipse は変数が初期化されていないことを警告するので、いずれにせよ非常に明白になります。個人的には、これがデフォルトの動作であることは良いことだと思います。そうしないと、アプリケーションが予期しない値を使用する可能性があり、コンパイラがエラーをスローする代わりに何もしません (ただし、おそらく警告を発します)。特定のものが本来あるべきように振る舞わない理由について頭を悩ませてください。

于 2009-01-06T08:10:27.983 に答える
-1

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

于 2013-03-04T10:53:53.903 に答える
-2

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

  1. ほとんどの回答が言ったように、ローカル変数を初期化するという制約を設定することで、プログラマーが望むようにローカル変数に値が割り当てられ、期待される結果が確実に計算されるようになります。
  2. インスタンス変数は、ローカル変数 (同じ名前) を宣言することで非表示にすることができます - 期待される動作を確実にするために、ローカル変数は強制的に値に初期化されます (ただし、私はこれを完全に避けます)。
于 2014-12-30T21:20:30.310 に答える