web-dev-qa-db-ja.com

1/0は正当なJava式ですか?

以下は私のEclipseでうまくコンパイルされます:

final int j = 1/0;
// compiles fine!!!
// throws ArithmeticException: / by zero at run-time

Javaは、多くの「ダムコード」が最初からコンパイルされないようにします(たとえば、"Five" instanceof Numberはコンパイルされません!)。したがって、これが警告ほど生成されなかったという事実は、私にとって非常に驚きでした。コンパイル時に定数式を最適化できるという事実を考慮すると、陰謀は深まります。

public class Div0 {
    public static void main(String[] args) {
        final int i = 2+3;
        final int j = 1/0;
        final int k = 9/2;
    }
}

上記のスニペットはEclipseでコンパイルされ、次のバイトコードを生成します(javap -c Div0

Compiled from "Div0.Java"
public class Div0 extends Java.lang.Object{
public Div0();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method Java/lang/Object."<init>":()V
   4:   return

public static void main(Java.lang.String[]);
  Code:
   0:   iconst_5
   1:   istore_1      // "i = 5;"
   2:   iconst_1
   3:   iconst_0
   4:   idiv
   5:   istore_2      // "j = 1/0;"
   6:   iconst_4
   7:   istore_3      // "k = 4;"
   8:   return

}

ご覧のとおり、ikの割り当てはコンパイル時の定数として最適化されていますが、0による除算(コンパイル時に検出可能である必要があります)はそのままコンパイルするだけです。

javac 1.6.0_17はさらに奇妙な動作をし、サイレントコンパイルを行いますが、ikへの割り当てをバイトコードから完全に削除します(おそらく、どこにも使用されていないと判断したため)。 1/0はそのままです(削除するとプログラムのセマンティクスがまったく異なるため)。

したがって、質問は次のとおりです。

  • 1/0は実際には合法的なJava式で、いつでもどこでもコンパイルする必要がありますか?
    • JLSはそれについて何と言っていますか?
  • これが合法である場合、それには正当な理由がありますか?
    • これはおそらくどのような利益をもたらすでしょうか?
37

1/0実際には合法的なJava式で、いつでもどこでもコンパイルする必要がありますか?

はい。

JLSはそれについて何と言っていますか?

具体的なことは何もありません...ゼロ除算は実行時例外になるということを除いて。ただし、JLSは、次の定義でランタイム例外が発生する可能性があることを認識しています。

「コンパイル時定数式は、プリミティブ型または文字列の値を示す式です突然完了しませんそして以下のみを使用して構成されます:...」

(強調が追加されました。)したがって、以下はコンパイルされません。

switch(i) {
    case 1:
    case 1 + 1: 
    case 1 / 0:  // compilation error.
}

これが合法である場合、それには正当な理由がありますか?

良い質問。 ArithmeticExceptionをスローする方法だと思いますが、それはもっともらしい理由ではありません。 Javaをこのように指定する理由としては、JLSとコンパイラが不必要に複雑になって、人を噛むことはめったにないEdgeケースに対処する必要がないためです。

しかし、これはすべてbyによるものです。事実は1/0は有効ですJavaコードであり、Javaコンパイラはこれをコンパイルエラーとしてフラグ付けするべきではありません。(Javaコンパイラーは、警告を発行します。ただし、それをオフにするコンパイラー・スイッチがあった場合に限ります。)

32
Stephen C

バグデータベースを掘り下げて、興味深い情報を見つけました。

バグID 4178182:JLSは1/0の動作を定数式として指定していません

次のコードは不正です:

class X { static final int i = 1 / 0; }

このコンパイル時定数の値は未定義ですしたがって、これはコンパイル時エラーである必要があります。ガイ・スティールは約18か月前にこれが確かに意図された振る舞い。

コンパイル時定数は、その値を静的に使用できるようにする必要があります(これにより、コンパイル時定数になります;-)たとえば、ゼロによる除算を含む定数によって値が決定される他の定数の値は定義されていません。これは、switchステートメントのセマンティクス、明確な割り当てと割り当て解除などに影響します。

バグID 4089107:javacは(定数)ゼロによる整数除算をエラーとして扱います

public class zero {
   public static void main(String[] args) {
      System.out.println(1/0);
   }
}

上記を実行すると、次のようになります。

zero.Java:3: Arithmetic exception.
     System.out.println(1/0);
                         ^
1 error

バグID 4154563:javacは、case式でゼロ定数式による除算を受け入れます。

次のテストをコンパイルしようとすると、Javaコンパイラがクラッシュします。このテストでもすべての1.2beta4コンパイラバージョンがクラッシュしますが、12.beta3にはバグがありません。例とコンパイラ診断は次のとおりです。

public class B {
   public static void main(String argv[]) {
      switch(0){
         case 0/0:
      }
  }
}

評価:コンパイラは、定数ゼロで除算しようとするすべての試行をコンパイル時エラーとして報告するために使用されていました。これはbeta3で修正され、定数ゼロで除算するためのコードが生成されるようになりました。残念ながら、このバグが発生しました。コンパイラは、case式のゼロ除算を適切に処理する必要があります。

結論

したがって、1/0をコンパイルする必要があるかどうかという問題は、論争の的となったトピックであり、Guy Steeleがコンパイル時のエラーであると主張する人もいれば、そうではないと言う人もいます。最終的には、コンパイル時エラーでもコンパイル時定数でもないと判断されたようです。

20

Java 明示的に必要ArithmeticExceptionをトリガーするためのゼロによる整数除算。 jへの割り当ては、仕様に違反するため、削除できません。

2
Marcelo Cantos

さて、Doubleクラスを調べると、次のことがわかります。

/**
 * A constant holding the positive infinity of type
 * <code>double</code>. It is equal to the value returned by
 * <code>Double.longBitsToDouble(0x7ff0000000000000L)</code>.
 */
public static final double POSITIVE_INFINITY = 1.0 / 0.0;

Doubleの代わりにfloatを使用することを除いて、Floatクラスでも同じ計算が行われます。基本的に、1/0は、Double.MAX_VALUEよりも大きい非常に大きな数値を返します。

この次のコード:

public static void main(String[] args) {
    System.out.println(Double.POSITIVE_INFINITY);
    System.out.println(Double.POSITIVE_INFINITY > Double.MAX_VALUE);
}

出力:

Infinity
true

Double.POSITIVE_INFINITYを印刷する際の特殊なケースに注意してください。ダブルと見なされますが、文字列を出力します。

質問に答えるために、はい、それはJavaで合法ですが、1/0は「無限大」に解決され、標準のDouble(またはfloatなど)とは異なる方法で処理されます。

私はそれがこのように実装された方法や理由について少しも手がかりがないことに注意する必要があります。上記の出力を見ると、すべてが黒魔術のように見えます。

2
Corey

コンパイラがコンパイル時に定数式をフォールドすることになっているのはどこにもないので、それは合法です。

「スマート」コンパイラは次のようにコンパイルできます。

a = 1 + 2

なので

a = 3

しかし、コンパイラがそうしなければならないということは何もありません。それ以外は、1/0は次のような法的な表現です。

int a;
int b;

a = a/b;

法的な表現です。

RUNTIMEで例外をスローしますが、それは理由によるランタイムエラーです。

0
Will Hartung

他の人はすでに1/0の合法性に答えているので、2番目の質問に移りましょう:

  • これが合法である場合、それには正当な理由がありますか?
    • これはおそらくどのような利益をもたらすでしょうか?

答えは次のようになります。

あなたの同僚をからかうため。; o)

同僚がコンピューターのロックを解除したまま部屋を出たら、アプリケーションの初期に使用されているクラスの静的初期化子のどこかにこっそり1/0を突っ込みます。このようにして、彼はアプリケーションの展開後(または展開中)に異常なArithmeticExceptionに遭遇することで十分にすぐにわかり、おそらくしばらくの間頭を悩ませるでしょう。このフェイルファストの方法を使用すると、比較的無害なジョークであることを確認できます。

追伸:うまくいきました。 ; o)

0
charlie

コンパイルの観点では合法ですが、実行すると例外がスローされます!

理由...適切なプログラミングでは柔軟性が必要であるため、入力するすべての式とすべてのコードはコンパイラの変数であり、数式ではX/[〜#〜] y [〜#〜]コンパイラは、Y変数の値が(Y ==)であるか、コンパイラの他の数値であるかを気にしません。これは変数です...コンパイラが値も確認する必要がある場合、それはランタイムと見なされますね。

0
TacB0sS

とにかくランタイムバリアントが必要になるのに、なぜコンパイル時にわざわざこれをキャッチするのですか?

たとえば、テキストファイルから「0」をロードして解析し、それで除算しようとした場合、Javaは、コンパイル時に何をしていたのかわかりません。その外部ファイルの内容を知っています。

また、任意の変数を0に設定して変数で除算する場合、Javaは、除算をキャッチするために、スクリプトのすべてのポイントですべての変数のすべての可能な値を追跡する必要があります。コンパイル時に0まで。

一貫性を保ち、実行時のみの例外にすることもできます。

0
Daniel