なぜ誰かが知っていますか:
public void foo()
{
System.out.println("Hello");
return;
System.out.println("World!");
}
Eclipseでは「到達不能エラー」として報告されますが、
public void foo()
{
System.out.println("Hello");
if(true) return;
System.out.println("World!");
}
「デッドコード」警告のみをトリガーしますか?
私が考えることができる唯一の説明は、Javaコンパイラは最初のフラグを立てるだけであり、Eclipseでの追加の分析は2番目のフラグを立てるということです。しかし、それが当てはまる場合、なぜJavaコンパイラは、コンパイル時にこのケースを理解しますか?
Javaコンパイラーは、コンパイル時にif(true)が影響を及ぼさず、本質的に同一のバイトコードを生成することを理解しませんか?到達可能なコード分析はどの時点で適用されますか?
この質問について考えるより一般的な方法は、「到達可能なコード分析はいつ適用されるのか」だと思います。 2番目のJavaコードフラグメントから最終バイトコードへの変換で、ある時点で "if(true)"ランタイム同等物が削除され、2つのプログラムの表現がJavaコンパイラは、到達可能なコード分析を再度適用しませんか?
最初はnotコンパイルを行い(エラーが発生しました)、2番目はコンパイルします(警告を取得しました)。それが違いです。
Eclipseがデッドコードを検出する理由については、JDKとは対照的に、この種のコードを検出するよりも微調整できる組み込みコンパイラーを備えた統合開発ツールの便利さです。
更新:JDKは実際にはデッドコードを排除します。
public class Test {
public void foo() {
System.out.println("foo");
if(true)return;
System.out.println("foo");
}
public void bar() {
System.out.println("bar");
if(false)return;
System.out.println("bar");
}
}
javap -c
さんのコメント:
public class Test extends Java.lang.Object { public Test(); Code: 0:aload_0 1:invokespecial#1 ; //メソッドJava/lang/Object。 "" :()V 4:return public void foo(); コード: 0:getstatic#2; //フィールドJava/lang/System.out:Ljava/io/PrintStream; 3:ldc#3; // String foo 5:invokevirtual#4; //メソッドJava/io/PrintStream.println:(Ljava/lang/StrV 8:return public void bar(); Code: 0:getstatic#2; // Field Java/lang/System.out:Ljava/io/PrintStream; 3:ldc#5; // String bar 5:invokevirtual# 4; //メソッドJava/io/PrintStream.println:(Ljava/lang/String;)V 8:getstatic#2; // Field Java/lang/System.out:Ljava/io/PrintStream; 11:ldc#5; //文字列バー 13:invokevirtual#4; //メソッドJava/io/PrintStream.println:(Ljava/lang/String;)V 16:戻る }
なぜ(Sun)はそれについて警告を出さないのか、私にはわかりません:)少なくともJDKコンパイラには実際にはDCE(Dead Code Elimination)が組み込まれています。
Java言語仕様 によると、到達不能コードはエラーです。
JLSから引用するには:
アイデアは、ステートメントを含むコンストラクター、メソッド、インスタンス初期化子、または静的初期化子の最初からステートメント自体への実行パスがいくつかある必要があるということです。分析では、ステートメントの構造が考慮されます。 while、do、および条件式の定数値がtrueであるステートメントの特別な処理を除いて、式の値はフロー分析では考慮されません。
つまり、if
ブロックは考慮されません。これは、if
ステートメントのパスの1つを通過すると、最後のprintステートメントに到達する可能性があるためです。コードを次のように変更した場合:
_public void foo() {
System.out.println("Hello");
if (true)
return;
else
return;
System.out.println("World!");
}
_
最後の行に到達できるif
ステートメントを経由するパスがないため、突然コンパイルされなくなります。
つまり、Java準拠のコンパイラでは、最初のコードフラグメントをコンパイルできません。JLSをさらに引用するには:
例として、次のステートメントはコンパイル時エラーになります。
_while (false) { x=3; }
_
ステートメントx = 3;到達できません。しかし、表面的には同様のケース:
_if (false) { x=3; }
_
コンパイル時エラーにはなりません。最適化コンパイラーは、ステートメントx = 3であることを認識できます。実行されることはなく、生成されたクラスファイルからそのステートメントのコードを省略できますが、ステートメントx = 3;ここで指定されている技術的な意味では、「到達不能」とは見なされません。
JLSによると、Eclipseがデッドコードに関してEclipseが出す2番目の警告は、コンパイラーが生成した警告であり、「到達不能」ではありませんが、実際にはそうです。これは、Eclipseが提供する追加の lint スタイルチェックです。これは完全にオプションであり、Eclipse構成を使用することにより、無効にするか、警告ではなくコンパイラエラーに変えることができます。
この2番目のブロックは「コードのにおい」であり、if (false)
ブロックは通常、デバッグ目的でコードを無効にするために配置されます。
実際、Eclipseはさらに高度なテストを行って、ifステートメントの可能な値を判別し、両方のパスを取ることが可能かどうかを判別します。たとえば、Eclipseは次のメソッドのデッドコードについても文句を言うでしょう。
_public void foo() {
System.out.println("Hello");
boolean bool = Random.nextBoolean();
if (bool)
return;
if (bool || Random.nextBoolean())
System.out.println("World!");
}
_
コードのこの時点ではbool
はfalse
のみでなければならないため、2番目のifステートメントの到達不能コードが生成されます。このような短いコードの断片では、2つのifステートメントが同じことをテストしていることは明らかですが、途中に10〜15行のコード行がある場合は、それほど明確ではない可能性があります。
つまり、2つの違いは、1つはJLSによって禁止されており、もう1つは禁止されていませんが、Eclipseによってプログラマーへのサービスとして検出されます。
これは、ある種の条件付きコンパイルを許可するためです。
これはif
のエラーではありませんが、コンパイラはwhile
、do-while
、およびfor
のエラーにフラグを立てます。
これで結構です:
if (true) return; // or false
System.out.println("doing something");
これはエラーです
while (true) {
}
System.out.println("unreachable");
while (false) {
System.out.println("unreachable");
}
do {
} while (true);
System.out.println("unreachable");
for(;;) {
}
System.out.println("unreachable");
JLS 14.21:Unreachable Statements の最後で説明されています。
この異なる処理の理論的根拠は、プログラマが次のような「フラグ変数」を定義できるようにすることです。
static final boolean DEBUG = false;
次に、次のようなコードを記述します。
if (DEBUG) { x=3; }
これは、DEBUGの値をfalseからtrueまたはtrueからfalseに変更してから、プログラムテキストを変更せずにコードを正しくコンパイルできるようにする必要があるという考え方です。
if (true)
は、「到達不能」よりも少し微妙です。ハードコードされたreturn
は常に次のコードに到達できないようにしますが、if
の条件を変更すると、次のステートメントに到達できるようになるためです。
条件があることは、条件が変わる可能性があることを意味します。かっこ内にtrue
よりも複雑なものが含まれる場合があり、人間の読者には次のコードが「無効化」されていることは明らかではありませんが、コンパイラーはそれを通知するので、警告することができます。
ここではEclipseについて触れていますが、ユーザーにとっては少し複雑に見えます。しかし、実際にはEclipseの下は単なる(非常に洗練された)Javaコンパイラーであり、Eclipseがオン/オフを切り替えることができる警告などのスイッチを多数備えています。つまり、まっすぐなjavac
コンパイルからさまざまな警告/エラーのかなりの範囲を取得することも、それらすべてをオンまたはオフにする便利な手段もないことですが、それは同じことですが、ベルとホイッスルが増えています。
到達できないコードの1つは間違いである可能性が高く、JLSはそのような間違いからユーザーを保護しようとすることです。
if (true) return;
を許可することは、JLSの制限を回避するための良い方法です。 JLSがこれを停止した場合、邪魔になります。さらに、それも停止する必要があります。
public static boolean DEBUG = true; //In some global class somewhere else
...
if (DEBUG) return; //in a completely unrelated class.
...
DEBUG定数は完全にインライン化されており、そのif条件でtrueを入力するだけの機能と同等です。 JLSの観点から、これら2つのケースは非常に似ています。
「Java Eclipseでのデッドコード警告)」という警告を無視したい場合は、Eclipse *内で次のようにします。
Eclipseを保存して閉じますIDE Eclipseを再び開くと、これらの特定の警告はリストに表示されなくなります。
*このサンプルソリューションでは、Eclipseを使用していますIDE for Java Developers-Version:Mars.2 Release(4.5.2)
違いは、実行時とコンパイル時のセマンティクスにあります。 2番目の例では、コードはバイトコードのif-elseブランチにコンパイルされます。Eclipseは、実行時にelse部分に到達しないことを伝えるだけの優れた機能を備えています。 Eclipseはまだ合法的なコードであるため、警告を出すだけです。
最初の例では、Javaの定義によりコードが不正であるため、エラーになります。コンパイラでは、到達できないステートメントを含むバイトコードを作成することはできません。
私はEclipseを試してみましたが、JDKのデッドコード処理には3つの種類があると思います。1)警告なし、2)警告、3)エラーです。
典型的な「IF」条件付きコンパイルコードの場合、JDKはこれを検出し、デッドコードとして報告しませんでした。定数ブールフラグによって引き起こされるデッドコードの場合、JDKはこれを検出し、警告レベルで報告します。プログラムの制御フローによって引き起こされるデッドコードについては、JDKはそれをエラーとして検出します。
以下は私の試みです:
public class Setting {
public static final boolean FianlDebugFlag = false;
}
class B {
.....
// no warn, it is typical "IF" conditional compilataion code
if(Setting.FianlDebugFlag)
System.out.println("am i dead?");
if(false)
System.out.println("am i dead?");
// warn, as the dead code is caused by a constant boolean flag
if(ret!=null && Setting.FianlDebugFlag)
System.out.println("am i dead?");
if(Setting.FinalDebug)
return null;
System.out.println("am i dea?");
// error, as the dead code is due to the program's control flow
return null;
System.out.println("am i dead");
}