53

Does anyone know why:

public void foo()
{
    System.out.println("Hello");
    return;
    System.out.println("World!");
}

Would be reported as an "unreachable error" under Eclipse, but

public void foo()
{
    System.out.println("Hello");
    if(true) return;
    System.out.println("World!");
}

Only triggers a "Dead code" warning?

The only explanation I can think of is that the Java compiler only flags the first, and that some extra analysis in Eclipse figures out the second. However, if that is the case, why can't the Java compiler figure out this case at compile time?

Wouldn't the Java compiler figure out at compile time that the if(true) has no effect, thus yielding bytecode that is essentially identical? At what point is the reachable code analysis applied?

I guess a more general way to think of this question is: "when is the reachable code analysis applied"? In the transformation of the second Java code fragment to the final bytecode, I am sure that at some point the "if(true)" runtime equivalent is removed, and the representations of the two programs become identical. Wouldn't the Java compiler then apply its reachable code analysis again?

4

8 に答える 8

34

1つ目はコンパイルされませ(エラーが発生します)、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言う:

パブリッククラスTestはjava.lang.Object{を拡張します
public Test();
  コード:
   0:aload_0
   1:invokespecial#1; //メソッドjava/lang/Object。"":()V
   4:戻る

public void foo();
  コード:
   0:getstatic#2; //フィールドjava/lang / System.out:Ljava / io / PrintStream;
   3:ldc#3; //文字列foo
   5:invokevirtual#4; //メソッドjava/io / PrintStream.println:(Ljava / lang / StrV
   8:戻る

public void bar();
  コード:
   0:getstatic#2; //フィールドjava/lang / System.out:Ljava / io / PrintStream;
   3:ldc#5; //文字列バー
   5:invokevirtual#4; //メソッドjava/io / PrintStream.println:(Ljava / lang / String;)V
   8:getstatic#2; //フィールド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)が組み込まれています。

于 2010-01-26T17:04:22.333 に答える
25

到達不能コードは、Java 言語仕様によるとエラーです。

JLSから引用するには:

アイデアは、ステートメントを含むコンストラクター、メソッド、インスタンス初期化子、または静的初期化子の先頭からステートメント自体までの実行パスが存在する必要があるということです。分析では、ステートメントの構造が考慮されます。while、do、および条件式の定数値が true であるステートメントの特別な処理を除いて、式の値はフロー分析では考慮されません。

つまり、ステートメントのパスの 1 つを通過すると、最終的な print ステートメントに到達する可能性があるため、ifブロックは考慮されません。ifコードを次のように変更した場合:

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; ここで指定された技術的な意味で「到達不能」とは見なされません。

デッド コードについて Eclipse が与える 2 番目の警告は、コンパイラによって生成される警告であり、JLS によれば「到達不能」ではありませんが、実際には到達可能です。これは、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!");
}

2 番目の if ステートメントに対して到達不能なコードが生成されます。これは、コードのこの時点でboolのみ存在する必要があると判断できるためです。falseこのような短いコード フラグメントでは、2 つの if ステートメントが同じことをテストしていることは明らかですが、途中に 10 ~ 15 行のコード行があると、それほど明白ではない可能性があります。

要約すると、2 つの違いは次のとおりです。1 つは JLS によって禁止されており、もう 1 つは禁止されていませんが、プログラマーへのサービスとして Eclipse によって検出されます。

于 2010-01-26T17:36:36.767 に答える
14

これは、一種の条件付きコンパイルを可能にするためです。
のエラーではありませんifが、コンパイラはwhiledo-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 に変更し、プログラム テキストに他の変更を加えることなくコードを正しくコンパイルできるようにする必要があります。

于 2010-01-26T18:15:16.757 に答える
2

これif (true)は「到達不能」よりも少し微妙です。ハードコーディングreturnすると、常に次のコードに到達できなくなりますが、の条件を変更ifすると、次のステートメントに到達できるようになる可能性があるためです。

そこに条件があるということは、条件が変わる可能性があることを意味します。括弧内にaよりも複雑なものがありtrue、次のコードが「無効」になっていることは人間の読者にはわかりませんが、コンパイラは気付くので、警告することができます。

ここではEclipseについて説明しますが、ユーザーにとっては少し複雑に見えるようになります。しかし実際には、Eclipseの下には(非常に洗練された)Javaコンパイラがあり、Eclipseがオンとオフを切り替えることができる警告などの多くのスイッチを備えています。言い換えると、ストレートjavacコンパイルからさまざまな警告/エラーを取得することはできません。また、それらすべてをオンまたはオフにする便利な手段もありません。しかし、それは同じ取引ですが、ベルとホイッスルが増えています。

于 2010-01-26T17:05:02.517 に答える
2

解決策の 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 つのケースは非常に似ています。

于 2010-01-26T17:47:23.263 に答える
0

違いは、実行時とコンパイル時のセマンティクスにあります。2 番目の例では、コードはバイトコードの if-else ブランチにコンパイルされます。Eclipse は、else 部分が実行時に到達しないことを示すのに十分スマートです。Eclipse はまだ正当なコードであるため、警告のみを表示します。

最初の例では、Java の定義によりコードが違法であるため、エラーになります。コンパイラは、到達不能なステートメントを含むバイト コードを作成することを許可しません。

于 2010-01-26T17:08:52.190 に答える
0

「Eclipse での Java でのデッド コード警告」という警告を無視する場合は、eclipse* 内で次の手順を実行します。

  1. Window-Preferences-Java-Compiler-Errors/Warnings をクリックします。
  2. 「潜在的なプログラミングの問題」をクリックします
  3. "Dead Code eg if(false)" を "Ignore" を選択します。
  4. [適用] をクリックします
  5. [OK] をクリックします。

Eclipse IDE を保存して閉じます。Eclipse を再度開くと、これらの特定の警告は表示されなくなります。

*このサンプル ソリューションでは、Java 開発者向けの Eclipse IDE を使用しています - バージョン: Mars.2 Release (4.5.2)

于 2016-03-29T11:00:38.790 に答える