83

while次の Javaの無限ループを見てください。その下のステートメントでコンパイル時エラーが発生します。

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の条件が常にであるにもかかわらず、その下のステートメントに対してコンパイラ エラーを発行しません。コンパイル時そのもの。iffalse

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たとしても、ブロックは常に実行されるためです。returntry

4

15 に答える 15

105

コンパイラは、最初の式が常に無限ループになることを簡単かつ明確に証明できますが、2 番目の式はそれほど簡単ではありません。あなたのおもちゃの例では簡単ですが、次の場合はどうでしょうか。

  • 変数の内容がファイルから読み込まれたか?
  • 変数はローカルではなく、別のスレッドによって変更される可能性がありますか?
  • 変数はユーザー入力に依存していましたか?

コンパイラは、その道を完全に放棄しているため、明らかに単純なケースをチェックしていません。なんで?仕様で禁止されている方がはるかに難しいからです。セクション 14.21を参照してください。

(ちなみに、私のコンパイラ、変数が宣言されているときに文句を言いますfinal。)

于 2011-12-20T02:58:02.083 に答える
55

仕様書によると、while文について次のように書かれています。

while ステートメントは、次のいずれかに該当する場合に正常に完了することができます。

  • while ステートメントは到達可能であり、条件式は値が true の定数式ではありません。
  • while ステートメントを終了する到達可能な break ステートメントがあります。\

そのため、コンパイラは、while 条件が true 値を持つ定数であるか、while 内に break ステートメントがある場合にのみ、while ステートメントに続くコードに到達できないと言います。2 番目のケースでは、b の値は定数ではないため、それに続くコードが到達不能であるとは見なされません。そのリンクの背後には、到達不能と見なされるものとそうでないものに関する詳細を提供する、さらに多くの情報があります。

于 2011-12-20T03:05:22.510 に答える
14

true は一定で、b はループ内で変更できるためです。

于 2011-12-20T02:59:01.483 に答える
10

変数の状態を分析するのは難しいため、コンパイラはほとんどあきらめて、ユーザーが望むことを実行できるようにします。さらに、Java 言語仕様には、コンパイラが到達不能コードを検出できる方法に関する明確な規則があります。

コンパイラをだます方法はたくさんあります - 別の一般的な例は

public void test()
{
    return;
    System.out.println("Hello");
}

コンパイラはその領域が到達不能であることに気付くため、これは機能しません。代わりに、あなたはすることができます

public void test()
{
    if (2 > 1) return;
    System.out.println("Hello");
}

これは、式が決して false にならないことをコンパイラが認識することができないため、機能します。

于 2011-12-20T02:56:33.720 に答える
6

後者は到達不能ではありません。ブール値の b は、ループ内のどこかで false に変更され、終了条件が発生する可能性があります。

于 2011-12-20T02:56:57.940 に答える
4

私の推測では、変数「b」はその値を変更する可能性があるため、コンパイラーは System.out.println("while terminated"); 到達できると考えています。

于 2011-12-20T02:58:07.273 に答える
4

コンパイラは完璧ではありません - 完璧であるべきでもありません

コンパイラの責任は構文を確認することであり、実行を確認することではありません。コンパイラは最終的に、厳密に型付けされた言語の多くの実行時の問題を検出して防止できますが、そのようなエラーをすべて検出することはできません。

実用的な解決策は、プリミティブ変数と停止条件に依存するのではなく、一連の単体テストを使用してコンパイラ チェックを補完するか、堅牢であることが知られているロジックを実装するためにオブジェクト指向コンポーネントを使用することです。

Strong Typing と OO : コンパイラの有効性を高める

一部のエラーは本質的に構文的なものです。Java では、強力な型付けにより、多くの実行時例外がキャッチ可能になります。ただし、より適切な型を使用することで、コンパイラがより適切なロジックを適用できるようになります。

コンパイラーにロジックをより効果的に適用させたい場合、Java での解決策は、プリミティブではなく、そのようなロジックを適用できる堅牢で必要なオブジェクトを構築し、それらのオブジェクトを使用してアプリケーションを構築することです。

これの古典的な例は、Java の foreach ループと組み合わせたイテレータ パターンの使用です。この構造は、単純化された while ループよりも、説明したタイプのバグに対して脆弱ではありません。

于 2011-12-20T03:04:23.370 に答える
3

実際、私は誰もそれをかなり正しく理解したとは思いません(少なくとも元の質問者の意味では)。OQは次のように言及し続けています。

正しいですが、bはループ内で変更されていないため、無関係です

しかし、最後の行に到達できるので、それは問題ではありません。そのコードを取得してクラスファイルにコンパイルし、クラスファイルを他の人(たとえばライブラリとして)に渡した場合、コンパイルされたクラスを、リフレクションによって「b」を変更するコードにリンクし、ループを終了して最後の原因となる可能性があります実行する行。

これは、定数ではない変数(または、使用されている場所で定数にコンパイルされるfinal-を参照するクラスではなく、finalを使用してクラスを再コンパイルすると、奇妙なエラーが発生する場合があります)に当てはまります。クラスはエラーなしで古い値を保持します)

リフレクション機能を使用して、別のクラスの非最終プライベート変数を変更し、購入したライブラリのクラスにモンキーパッチを適用しました。これにより、ベンダーからの公式パッチを待つ間、開発を続行できるようにバグが修正されました。

ちなみに、これは最近実際には機能しない可能性があります-私は以前にそれを行いましたが、そのような小さなループがCPUキャッシュにキャッシュされる可能性があり、変数が揮発性としてマークされていないため、キャッシュされたコードは決して機能しない可能性があります新しい値を取得します。私はこれが実際に行われているのを見たことがありませんが、理論的には正しいと思います。

于 2011-12-20T19:16:53.627 に答える
3

コンパイラは、含まれている可能性のある値を実行するほど洗練されていませんb(ただし、一度しか割り当てません)。最初の例は、条件が変数ではないため、無限ループになることをコンパイラーが簡単に確認できます。

于 2011-12-20T02:56:05.600 に答える
3

あなたのコンパイラが最初のケースのコンパイルを拒否したことに驚いています。それは私には奇妙に思えます。

bただし、(a) 別のスレッドが の値を更新する可能性があるため(b) 呼び出された関数がb副作用としての値を変更する可能性があるため、2 番目のケースは最初のケースに最適化されていません。

于 2011-12-20T02:57:25.057 に答える
3

これは単純に、可能ではありますが、コンパイラーがベビーシッターの作業をあまり行わないためです。

示されている例は単純であり、コンパイラーが無限ループを検出するのに妥当です。しかし、 variable に関係なく 1000 行のコードを挿入するのはどうbでしょうか? そして、それらのステートメントはすべてb = true;どうですか?コンパイラは間違いなく結果を評価し、最終的にwhileループ内でそれが真であることを伝えますが、実際のプロジェクトをコンパイルするのはどれくらい遅くなるでしょうか?

PS、lintツールは間違いなくあなたのためにそれを行うべきです。

于 2011-12-20T05:48:01.380 に答える
2

最初のステートメントは、while ループの条件で定数を指定しているため、常に無限ループになります。2 番目のケースのように、コンパイラーは、ループ内で b の値が変更される可能性があると想定しています。

于 2011-12-21T06:38:22.877 に答える
2

コンパイラの観点からは、binwhile(b)がどこかで false に変わる可能性があります。コンパイラはチェックを気にしません。

お楽しみwhile(1 < 2)for(int i = 0; i < 1; i--)など

于 2011-12-20T02:59:23.147 に答える
2

trueブール値が実行時に評価されるとコンパイラが最終的に判断できる場合、コンパイラはそのエラーをスローします。コンパイラーは、宣言した変数が変更可能であると想定します(ただし、ここでは変更できないことがわかっています)。

この事実を強調するために、変数がfinalJava のように宣言されている場合、ほとんどのコンパイラは、値を代入した場合と同じエラーをスローします。これは、変数がコンパイル時に定義されている (実行時に変更できない) ためです。したがって、コンパイラは、式がtrue実行時に評価されることを最終的に判断できます。

于 2011-12-20T03:04:16.193 に答える
2

式は実行時に評価されるため、スカラー値「true」をブール変数などに置き換えると、スカラー値がブール式に変更されるため、コンパイラーはコンパイル時にそれを知る方法がありません。

于 2011-12-20T03:13:17.130 に答える