Javaの無限ループ


82

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

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

これはfinallyreturnステートメントがtryブロック自体の前にある場合でも、ブロックが常に実行されるためです。


46
誰も気にしない?これは明らかにコンパイラの機能にすぎません。この種のものについては気にしないでください。
CJ7 2011

17
別のスレッドがローカルの非静的変数をどのように変更できますか?
CJ7 2011

4
オブジェクトの内部状態は、リフレクションによって同時に変更される場合があります。そのため、JLSは、最終的な(定数)式のみをチェックすることを義務付けています。
lsoliveira 2011

5
私はこれらの愚かなエラーが嫌いです。到達不能コードは、エラーではなく警告である必要があります。
user606723 2011

2
@ CJ7:これを「機能」とは呼びません。準拠するJavaコンパイラを実装するのは(理由もなく)非常に面倒です。設計によるベンダーロックインをお楽しみください。
L̳o̳̳n̳̳g̳̳p̳o̳̳k̳̳e̳̳

回答:


105

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

  • 変数の内容はファイルから読み取られましたか?
  • 変数はローカルではなく、別のスレッドで変更できますか?
  • 変数はユーザー入力に依存していましたか?

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

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


25
-1-コンパイラが何ができるかではありません。チェックが簡単または難しいということではありません。これは、Java言語仕様によってコンパイラが実行できることについてです。コードの2番目のバージョンはJLSによると有効なJavaであるため、コンパイルエラーは間違っています。
スティーブンC

10
@ StephenC-その情報をありがとう。喜んで更新され、同じくらい反映されます。
ウェイン

それでも-1; ここでは、「whatifs」は適用されません。現状では、コンパイラー実際に問題を解決できます。静的分析の用語では、これは定数伝搬と呼ばれ、他の多くの状況で広く使用されています。ここでの唯一の理由はJLSであり、問題の解決がどれほど難しいかは重要ではありません。もちろん、そもそもJLSがそのように書かれている理由は、その問題の難しさに関係しているのかもしれませんが、個人的には、異なるコンパイラ間で標準化された定数伝搬ソリューションを適用することが難しいためだと思います。
オーク

2
@ Oak-私は自分の答えで「[OPの]おもちゃの例では簡単だ」と認め、スティーブンのコメントに基づいて、JLSが制限要因であると認めました。私たちは同意すると確信しています。
ウェイン

55

仕様によると、whileステートメントについて次のように述べられています。

whileステートメントは、次の少なくとも1つが当てはまる場合に正常に完了することができます。

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

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


3
+1は、コンパイラの作成者が考えられること、または考えられないことだけではないことに注意してください。JLSは、到達不能と見なすことができることとできないことを伝えます。
yshavit 2011

14

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


1
正しいが、無関係な、bとされないループに変更されます。trueを使用するループには、breakステートメント含まれる可能性があることも同様に主張できます。
deworde 2011

@ deworde-(たとえば別のスレッドによって)変更される可能性がある限り、コンパイラーはそれをエラーと呼ぶことはできません。ループの実行中にbが変更されるかどうかは、ループ自体を見ただけではわかりません。
Peter Recore 2011

10

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

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

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

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

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

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


6
他の回答が述べているように、これは、コンパイラが到達不能コードを検出するのが簡単または難しい可能性があるものとは(直接)関係していません。JLSは、到達不能なステートメントを検出するためのルールを指定するために非常に長い時間を費やしています。これらのルールによると、最初の例は有効なJavaではなく、2番目の例は有効なJavaです。それでおしまい。コンパイラは、指定されたとおりにルールを実装するだけです。
スティーブンC

私はJLSの議論に従わない。コンパイラは本当にすべての正当なJavaをバイトコードに変換する義務がありますか?無意味であることが証明できたとしても。
emory 2011

@emoryはい、それは標準に準拠したブラウザの定義であり、標準に準拠し、正当なJavaコードをコンパイルします。また、標準に準拠していないブラウザを使用している場合、いくつかの優れた機能があるかもしれませんが、「賢明な」ルールとは何かについて、忙しいコンパイラ設計者の理論に頼っています。「なぜ誰もが使いたいでしょう:どの本当にひどいシナリオですTWO私が持っていたことがない場合と同じで条件文を?」)
deworde

この使用例ifwhile、コンパイラがif(true) return; System.out.println("Hello");文句を言わずにシーケンスをコンパイルするため、とは少し異なります。IIRC、これはJLSの特別な例外です。コンパイラは、とif同じくらい簡単に到達不能コードを検出できるようになりますwhile
クリスチャンセムラウ2011

2
@ emory-サードパーティのアノテーションプロセッサを介してJavaプログラムにフィードする場合、それは...非常に現実的な意味で...もはやJavaではありません。むしろ、アノテーションプロセッサが実装する言語拡張や変更がJavaにオーバーレイされています。しかし、それはここで起こっていることではありません。OPの質問はバニラJavaに関するものであり、JLSルールは有効なバニラJavaプログラムとは何かを管理します。
スティーブンC

6

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


bはループ内で変更されていないため、正しいですが、無関係です。trueを使用するループには、終了条件を与えるbreakステートメントが含まれる可能性があることも同様に主張できます。
deworde 2011

4

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


4

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

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

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

強いタイピングとOO:コンパイラの有効性を高める

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

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

この典型的な例は、イテレータパターンの使用であり、Javaのforeachループと組み合わせて、この構造は、単純なwhileループよりも説明するタイプのバグに対する脆弱性が低くなっています。


静的な強い型付けメカニズムがエラーを見つけるのを助ける方法はOOだけではないことに注意してください。Haskellのパラメトリック型と型クラス(オブジェクト指向言語でクラスと呼ばれるものとは少し異なります)は、実際にはこれでかなり間違いなく優れています。
左回り2011

3

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


4
これは、コンパイラの「洗練度」とは関係ありません。JLSは、到達不能なステートメントを検出するためのルールを指定するために非常に長い時間を費やしています。これらのルールによると、最初の例は有効なJavaではなく、2番目の例は有効なJavaです。それでおしまい。コンパイラの作成者は、指定されたとおりにルールを実装する必要があります...そうでない場合、コンパイラは不適合です。
スティーブンC

3

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

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


2
驚いたら、Java言語仕様を十分に読んでいません:-)
Stephen C

1
ハハ:)私がどこに立っているかを知ってうれしいです。ありがとう!
sarnold 2011

3

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

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

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

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

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

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


いい視点ね。それbはメソッド変数だと思います。リフレクションを使用してメソッド変数を本当に変更できますか?全体的なポイントに関係なく、最後の行に到達できないと想定してはなりません。
emory 2011

痛い、あなたは正しい、私はbがメンバーだったと思いました。bがメソッド変数の場合、コンパイラーはそれが変更されないことを「できる」と信じていますが、変更されません(これにより、他のすべての回答は結局非常に正しくなります)
Bill K

3

それは可能ですが、コンパイラがベビーシッターの仕事をあまりしすぎないからです。

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

PS、lintツールは間違いなくあなたのためにそれを行う必要があります。


2

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

楽しみのためにwhile(1 < 2)for(int i = 0; i < 1; i--)などを試してみてください。


2

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


2

コンパイラがブール値がtrue実行時に評価されると決定的に決定できる場合、そのエラーがスローされます。コンパイラーは、宣言した変数が変更可能であると想定しています(ただし、ここでは人間としては変更できません)。

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


2

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.