Javaで次の無限while
ループを見てください。その下のステートメントでコンパイルエラーが発生します。
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
次の同じ無限while
ループは正常に機能し、条件をブール変数に置き換えただけのエラーは発生しません。
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
2番目のケースでも、ブール変数b
がtrueであるため、コンパイラーがまったく文句を言わないため、ループ後のステートメントは明らかに到達できません。どうして?
編集:次のバージョンのwhile
は明らかなように無限ループに陥りますが、ループ内のif
条件にもかかわらず、その下のステートメントに対してコンパイラエラーを発行しませんは常にfalse
であり、その結果、ループが戻ることはなく、コンパイラーはコンパイル時にそれ自体を判別できます。
while(true) {
if(false) {
break;
}
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
while(true) {
if(false) { //if true then also
return; //Replacing return with break fixes the following error.
}
System.out.println("inside while");
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
while(true) {
if(true) {
System.out.println("inside if");
return;
}
System.out.println("inside while"); //No error here.
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
編集:if
およびwhile
でも同じです。
if(false) {
System.out.println("inside if"); //No error here.
}
while(false) {
System.out.println("inside while");
// Compiler's complain - unreachable statement.
}
while(true) {
if(true) {
System.out.println("inside if");
break;
}
System.out.println("inside while"); //No error here.
}
次のバージョンのwhile
も無限ループに陥ります。
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
これは、finally
ブロック自体の中でreturn
ステートメントがその前に出現しても、try
ブロックは常に実行されるためです。
コンパイラーは、最初の式alwaysが無限ループになることを簡単かつ明確に証明できますが、2番目の式ほど簡単ではありません。おもちゃの例では簡単ですが、次の場合はどうでしょう。
コンパイラーは、単純なケースをチェックしていないことは明らかです。なぜなら、それは完全にその道を離れているからです。どうして?だってそれは はるかに難しい 仕様により禁止されています。 セクション14.21 を参照してください:
(ちなみに、私のコンパイラdoesは、変数がfinal
と宣言されると文句を言います。)
仕様 によると、whileステートメントについて次のように述べられています。
Whileステートメントは、以下の少なくとも1つが当てはまる場合に通常どおり完了できます。
- Whileステートメントに到達可能であり、条件式が値trueの定数式ではありません。
- Whileステートメントを終了する到達可能なbreakステートメントがあります。\
したがって、コンパイラーは、while条件がtrueの値を持つ定数である場合、またはwhile内にbreakステートメントがある場合にのみ、whileステートメントに続くコードに到達できないと言います。 2番目のケースでは、bの値は定数ではないため、それに続くコードは到達不能とは見なされません。そのリンクの背後には、到達不能と見なされるものとそうでないものについての詳細を提供する、より多くの情報があります。
Trueは定数であり、bはループで変更できるためです。
変数の状態を分析するのは難しいので、コンパイラはほとんどあきらめて、あなたが望むことをさせます。さらに、Java言語仕様には コンパイラが到達不能コードを検出する方法 に関する明確なルールがあります。
コンパイラをだます方法はたくさんあります-別の一般的な例は
public void test()
{
return;
System.out.println("Hello");
}
コンパイラーはその領域が到達不能であることを認識するため、これは機能しません。代わりに、あなたはできる
public void test()
{
if (2 > 1) return;
System.out.println("Hello");
}
コンパイラは式が決して偽になることを認識できないため、これは機能します。
後者は到達不可能ではありません。ブールbは、ループ内のどこかでfalseに変更され、終了条件を引き起こす可能性があります。
変数 "b"はその値を変更する可能性があるため、コンパイラはSystem.out.println("while terminated");
に到達できると思います。
コンパイラーは完璧ではありません-完璧ではありません
コンパイラの責任は構文を確認することです-実行を確認することではありません。コンパイラは最終的に、強く型付けされた言語の多くの実行時の問題を検出して防止できますが、そのようなエラーをすべて検出できるわけではありません。
実用的な解決策は、コンパイラーチェックを補完する単体テストのバッテリーを用意することですORプリミティブ変数や停止条件に依存するのではなく、堅牢であることがわかっているロジックを実装するためのオブジェクト指向コンポーネントを使用します。
強力なタイピングとOO:コンパイラーの効率を上げる
いくつかのエラーは本質的に構文です-そしてJavaでは、強い型付けは多くの実行時例外をキャッチ可能にします。しかし、より優れた型を使用することで、コンパイラーがより優れたロジックを実施するのを支援できます。
コンパイラーにロジックをより効果的に適用させたい場合、Javaでは、そのようなロジックを適用できる堅牢な必須オブジェクトを構築し、それらのオブジェクトを使用して、プリミティブではなくアプリケーションを構築します。
この典型的な例は、イテレーターパターンの使用とJavaのforeachループの組み合わせであり、この構造は単純なwhileループよりも、説明するタイプのバグに対して脆弱ではありません。
実際、私はだれもそれを非常に正しく理解しているとは思いません(少なくとも元の質問者の意味では)。 OQは言及し続けます:
Bはループで変更されていないため、正しいが無関係です。
しかし、最後の行IS到達可能です。そのコードを取得してクラスファイルにコンパイルし、そのクラスファイルを他の人(ライブラリなど)に渡した場合、コンパイルされたクラスを、リフレクションによって「b」を変更するコードとリンクし、ループを終了して、最後の行を実行させることができます。
これは、定数(またはそれが使用される場所で定数にコンパイルされるfinal)でないすべての変数に当てはまります-参照するクラスではなく、finalでクラスを再コンパイルすると、奇妙なエラーが発生することがありますクラスは古い値を保持し、エラーは発生しません)
リフレクションの機能を使用して、別のクラスの最終ではないプライベート変数を変更して、購入したライブラリのクラスにサルパッチを適用しました-ベンダーからの公式パッチを待つ間、開発を継続できるようにバグを修正しました。
ちなみに、これは最近実際には機能しない可能性があります-以前に実行したことはありますが、そのような小さなループがCPUキャッシュにキャッシュされる可能性があり、変数が揮発性としてマークされていないため、キャッシュされたコードは決して新しい値を取得します。私はこれを実際に見たことがありませんが、理論的には正しいと思います。
コンパイラは、b
に含まれる可能性のある値を実行できるほど高度ではありません(割り当ては1回だけです)。最初の例は、条件が可変ではないため、コンパイラーが無限ループになることを簡単に確認できます。
コンパイラが最初のケースのコンパイルを拒否したことに驚いています。それは私には奇妙に思えます。
ただし、(a)別のスレッドがb
の値を更新する可能性があるため、2番目のケースは最初のケースに最適化されていません(b)呼び出された関数がb
の値を変更する可能性がある。
それは可能ですが、コンパイラーがベビーシッター作業をあまり行わないからです。
示されている例は、コンパイラーが無限ループを検出するためにシンプルで合理的です。しかし、変数b
とは関係なく、1000行のコードを挿入するのはどうでしょうか。そして、それらのステートメントはすべてb = true;
?コンパイラは確実に結果を評価し、最終的にwhile
ループで真であることを伝えることができますが、実際のプロジェクトをコンパイルするのはどのくらい遅いでしょうか?
PS、lintツールは間違いなくそれを行うはずです。
コンパイラの観点から見ると、while(b)
のb
はどこかでfalseに変わる可能性があります。コンパイラーはチェックを気にしません。
楽しみには、while(1 < 2)
、for(int i = 0; i < 1; i--)
などをお試しください。
コンパイラーが実行時にブール値がtrue
に評価されると決定的に判断できる場合、そのエラーがスローされます。コンパイラーは、宣言した変数canが変更されると想定します(ただし、ここでは人間としては変更されません)。
この事実を強調するために、Javaで変数がfinal
として宣言されている場合、ほとんどのコンパイラーは、値を置換した場合と同じエラーをスローします。これは、変数がコンパイル時に定義され(実行時に変更できない)、コンパイラーが実行時に式がtrue
に評価されると決定的に判断できるためです。
Whileループの条件で定数を指定しているため、最初のStatementは常に無限ループになります。2番目の場合と同様に、コンパイラーはループ内のbの値が変更される可能性があると想定しています。
式は実行時に評価されるため、スカラー値「true」をブール変数のようなものに置き換えると、スカラー値をブール式に変更したため、コンパイラーはコンパイル時にそれを知ることができません。