web-dev-qa-db-ja.com

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

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

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

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

とてもイライラします。

93

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

55
Warrior

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

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番目のバージョンのコードは、 correct で記述する方法です。最初のバージョンでは、コンパイラのエラーメッセージは正しいものでした。 so変数は初期化されていない可能性があります。特に、SomeObjectコンストラクターが失敗した場合、soは初期化されないため、so.CleanUpを呼び出そうとするとエラーになります。常にtryセクションを入力してください after 後、finallyセクションがファイナライズするリソースを取得しました。

try初期化後のfinally-soブロックは、SomeObjectインスタンスを保護し、確実に取得するために only があります他に何があってもクリーンアップします。 other を実行する必要があるが、SomeObjectインスタンスがプロパティが割り当てられているかどうかに関係がない場合は、 another try-finallyブロック、おそらく私が示したものをラップするブロック。

使用する前に変数を手動で割り当てる必要がある場合、実際の問題は発生しません。それはちょっとした面倒をもたらすだけですが、あなたのコードはそれのためにより良いでしょう。より限定されたスコープを持つ変数と、あまり保護しようとしないtry-finallyブロックがあります。

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

22
Rob Kennedy

さらに、以下の例では、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
Electric Monk

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

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

10
Adeel Ansari

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

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

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

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

それに加えて、「int size」ではなく「int size = 0」と明示的に言って、次のプログラマにゼロにするつもりであることを理解させる方が良いと思いませんか?

反対に、コンパイラーにすべての初期化されていない変数を0に初期化させる正当な理由を考え出すことはできません。

8
Bill K

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

4
Mehrdad Afshari

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

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

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

ただし、デフォルトのローカル変数の背後にあるnotを取得できます。はい、私たちはロジックをダブルチェックするためにコンパイラに依存すべきではありませんし、そうではありませんが、コンパイラがそれを見つけたときにそれはまだ便利です。 :-)

3
T.J. Crowder

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

変数を初期化する必要がある場合は、いつでも自分で行うことができますので、問題はありません。

3
starblue

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

たとえば、次の単純なコードを考えてみてください...(N.B。明示的に初期化されていない場合、ローカル変数には指定されたデフォルト値が割り当てられていると想定してみましょう

_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
_

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

たとえば、オブジェクトがコンストラクターでチェック例外をスローするとき、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を使用して、この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の各要素は文字列を指す必要があります(またはnullです)。ただし、初期値がこのメモリ位置で発生する一連の0と1である場合、毎回同じ結果が得られるという保証がないだけでなく、オブジェクトs [0]が指すという保証もありませんto(それが意味のあるものを指していると仮定して)でもis文字列(おそらくそれはウサギ、:p)です!このタイプの懸念の欠如は、Java Javaを作成するほとんどすべての問題に直面します。変数は必要性に近い。

1
kumquatfelafel

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

0
Kezzer

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

0

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

0
Xiaogang