Java で「ステートメントに到達できません」というコンパイラ エラーが発生するのはなぜですか?

StackOverflow https://stackoverflow.com/questions/3795585

質問

プログラムをデバッグするときに、コード ブロック内に return ステートメントを挿入すると便利であることがよくわかります (おそらく悪い習慣ですが)。Java でこのようなことを試してみるかもしれません...

class Test {
        public static void main(String args[]) {
                System.out.println("hello world");
                return;
                System.out.println("i think this line might cause a problem");
        }
}

もちろん、これではコンパイラ エラーが発生します。

テスト.java:7:到達不能なステートメント

未使用のコードを使用することは悪い習慣であるため、警告が正当化される理由が理解できました。しかし、なぜこれでエラーが発生する必要があるのか​​わかりません。

これは単に Java が乳母になろうとしているだけなのでしょうか、それともこれをコンパイラ エラーにする十分な理由があるのでしょうか?

役に立ちましたか?

解決

到達不能なコードはコンパイラにとって意味がないからです。コードを人々にとって意味のあるものにすることは、コンパイラにとってコードを意味のあるものにすることよりも最も重要かつ困難ですが、コンパイラはコードの重要な消費者です。Java の設計者は、コンパイラにとって意味のないコードはエラーであるという観点をとります。彼らのスタンスは、到達できないコードがある場合は、修正する必要がある間違いを犯しているということです。

ここにも同様の質問があります。 到達不能なコード:エラーまたは警告?, その中で著者は「個人的には、これは誤りであるべきだと強く感じます。プログラマーがコードを書く場合、常にそれを何らかのシナリオで実際に実行することを念頭に置く必要があります。」明らかに Java の言語設計者もこれに同意しています。

到達不能なコードがコンパイルを妨げるべきかどうかは、決して合意が得られない問題です。しかし、これが Java 設計者がそれを行った理由です。


コメントで多くの人が、Java ではコンパイルが妨げられない、到達不能なコードのクラスが多数あることを指摘しています。ゲーデルの結果を正しく理解すれば、到達不可能なコードのすべてのクラスをキャッチできるコンパイラは存在しないでしょう。

単体テストではすべてのバグを検出できるわけではありません。私たちはこれをその価値に対する議論として使用しません。同様に、コンパイラは問題のあるコードをすべて検出できるわけではありませんが、可能であれば不正なコードのコンパイルを防ぐことは価値があります。

Java 言語の設計者は、到達不能なコードをエラーとみなします。したがって、可能な場合はコンパイルを防止するのが合理的です。


(反対票を投じる前に:問題は、Java に到達不能ステートメントのコンパイラ エラーが発生するかどうかではありません。質問は なぜ Java に到達不能なステートメントのコンパイラ エラーが発生しました。Java の設計上の決定が間違っていると思うからといって、私に反対票を投じないでください。)

他のヒント

到達不能なステートメントを許可してはならない決定的な理由はありません。他の言語では問題なく使用できます。特定のニーズに対応するための通常の方法は次のとおりです。

if (true) return;

これはナンセンスに見えますが、コードを読む人は誰でも、これは残りのステートメントを到達不能のままにするという不注意なミスではなく、意図的に行われたに違いないと推測するでしょう。

Java は「条件付きコンパイル」を少しサポートしています

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

if (false) { x=3; }

コンパイル時間エラーは発生しません。最適化コンパイラは、ステートメントx = 3であることを認識する場合があります。実行されることはなく、生成されたクラスファイルからそのステートメントのコードを省略することを選択できますが、ステートメントx = 3。ここで指定されている技術的な意味では、「到達不可能」とは見なされません。

この異なる治療の理論的根拠は、プログラマーが次のような「フラグ変数」を定義できるようにすることです。

static final boolean DEBUG = false;

そして次のようなコードを書きます。

if (DEBUG) { x=3; }

アイデアは、デバッグの値をfalseからtrueに、またはtrueからfalseに変更し、プログラムテキストに他の変更なしでコードを正しくコンパイルすることができるはずです。

ナニーです。.Net はこれを正しく理解していると思います。到達不能なコードについては警告が表示されますが、エラーは表示されません。それについて警告されるのは良いことですが、コンパイルを妨げる理由はありません (特に、一部のコードをバイパスするためにリターンをスローすることが望ましいデバッグ セッション中)。

私はちょうどこの質問に気づいたので、これに 0.02 ドルを追加したいと思いました。

Java の場合、これは実際にはオプションではありません。「コードに到達できません」エラーは、JVM 開発者が開発者をあらゆるものから保護しようと考えたり、特別に警戒したりしたという事実からではなく、JVM 仕様の要件から発生しています。

Java コンパイラと JVM は両方とも、「スタック マップ」と呼ばれるものを使用します。これは、現在のメソッドに割り当てられた、スタック上のすべての項目に関する明確な情報です。JVM 命令があるタイプの項目を別のタイプで誤って扱わないように、スタックのすべてのスロットのタイプを知っておく必要があります。これは、数値がポインターとして使用されるのを防ぐために最も重要です。Java アセンブリを使用すると、数値をプッシュ/ストアしようとしながら、オブジェクト参照をポップ/ロードすることが可能です。ただし、JVM はクラスの検証中、つまりスタック マップが作成され、一貫性がテストされているときにこのコードを拒否します。

スタック マップを検証するには、VM はメソッド内に存在するすべてのコード パスをたどり、どのコード パスが実行されるかに関係なく、すべての命令のスタック データが以前のコードがプッシュしたものと一致することを確認する必要があります。 /スタックに保存されます。したがって、次のような単純な場合です。

Object a;
if (something) { a = new Object(); } else { a = new String(); }
System.out.println(a);

3 行目で、JVM は 'if' の両方の分岐が Object と互換性のあるもの (単なるローカル var#0) にのみ格納されているかどうかを確認します (3 行目以降のコードがローカル var#0 を扱う方法であるため) )。

コンパイラーが到達不能なコードに到達した場合、コンパイラーはその時点でスタックがどのような状態にあるのかを完全に把握していないため、その状態を検証できません。その時点では、ローカル変数を追跡することもできないため、コードを完全にコンパイルすることはできなくなります。そのため、クラス ファイルにこの曖昧さを残す代わりに、致命的なエラーが生成されます。

もちろん、次のような単純な条件 if (1<2) これはそれをだましますが、実際にはだましているわけではありません。コードにつながる可能性のある分岐を与えており、少なくともコンパイラと VM の両方が、そこからスタック項目をどのように使用できるかを決定できます。

追伸この場合 .NET が何をするかはわかりませんが、コンパイルも失敗すると思います。これは通常、どのマシン コード コンパイラ (C、C++、Obj-C など) にとっても問題になりません。

コンパイラーの目標の 1 つは、エラーのクラスを除外することです。到達不能なコードが偶然存在する場合がありますが、javac がコンパイル時にそのクラスのエラーを除外するのは素晴らしいことです。

エラーのあるコードをキャッチするルールごとに、自分が何をしているのかを知っているため、コンパイラーにそれを受け入れてもらいたいと考える人がいます。これはコンパイラ チェックのペナルティであり、バランスを適切に保つことが言語設計の難しい点の 1 つです。最も厳密なチェックを行ったとしても、作成できるプログラムは無限にあるため、事態はそれほど悪くはなりません。

このコンパイラ エラーは良いことだと思いますが、回避する方法があります。true であることがわかっている条件を使用します。

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

コンパイラはそれについて不平を言うほど賢明ではありません。

必要なことを実行できる限り、コンパイラが厳格であるほど優れていると文句を言うのは確かに良いことです。通常、支払うべき小さな代償はコードをコメントアウトすることであり、コードをコンパイルすると機能するという利点があります。一般的な例は Haskell で、テスト/デバッグがメイン テストのみで短いものであることに気づくまで、人々は叫び続けます。私自身、Java では (実際には意図的に) 注意力を欠いてデバッグをほとんどしません。

許可する理由なら if (aBooleanVariable) return; someMoreCode; フラグを許可するということは、 if (true) return; someMoreCode; コンパイル時エラーを生成しないことは、コンパイラがそれを「知っている」ため、CodeNotReachable 例外を生成するポリシーに矛盾があるように見えます。 true はフラグではありません (変数ではありません)。

他に 2 つの方法は興味深いかもしれませんが、メソッドのコードの一部をオフにすることや、 if (true) return:

さて、言う代わりに if (true) return; 言いたいかもしれない assert false そして追加します -ea OR -ea package OR -ea className jvm 引数に。良い点は、これによりある程度の粒度が可能になり、jvm 呼び出しに追加のパラメーターを追加する必要があるため、コード内で DEBUG フラグを設定する必要がなく、実行時に引数を追加する必要がないことです。これは、ターゲットが開発者マシンとバイトコードの再コンパイルと転送には時間がかかります。

もあります。 System.exit(0) ただし、これはやりすぎかもしれません。これを JSP 内の Java に置くと、サーバーが終了します。

Java が設計上「乳母」言語であることは別として、より制御するには C/C++ のようなネイティブなものを使用したいと思います。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top