41

以下在我的 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

}

如您所见,iandk赋值被优化为编译时常量,但除以0(必须在编译时检测到)只是按原样编译。

javac 1.6.0_17行为更加奇怪,静默编译,但将分配i完全k从字节码中删除(可能是因为它确定它们没有在任何地方使用)但保持1/0原样(因为删除它会导致完全不同的程序语义)。

所以问题是:

  • 实际上是1/0一个可以随时随地编译的合法 Java 表达式吗?
    • JLS 对此有何评论?
  • 如果这是合法的,有充分的理由吗?
    • 这可能有什么好处?
4

8 回答 8

34

实际上是1/0一个可以随时随地编译的合法 Java 表达式吗?

是的。

JLS 对此有何评论?

没有什么特别的......除了说被零除会导致运行时异常。但是,JLS 在以下定义中承认运行时异常的可能性:

“编译时常量表达式是表示原始类型值或字符串的表达式,它不会突然完成并且仅使用以下内容组成:...”

(添加了重点。)所以以下内容不会编译:

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

如果这是合法的,有充分的理由吗?

好问题。我认为这是一种投掷方式,ArithmeticException尽管这不是一个合理的理由。以这种方式指定 Java 的一个更可能的原因是避免 JLS 和编译器中不必要的复杂性来处理很少会咬人的边缘情况。

但这一切都是过去的。事实是这1/0是有效的 Java 代码,任何 Java 编译器都不应该将其标记为编译错误。(如果有一个编译器开关可以关闭它,Java 编译器发出警告是合理的。)

于 2010-05-29T06:28:35.160 回答
20

我对 Bug 数据库进行了一些挖掘,并发现了一些有趣的信息。

错误号 4178182:JLS 没有将 1/0 的行为指定为常量表达式

以下代码是非法的:

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

此编译时常量的值未定义,因此这必须是编译时错误。Guy Steele 大约 18 个月前证实,这确实是预期的行为。

编译时常量必须具有静态可用的值(这就是使它成为编译时常量的原因;-)例如,其他常量的值由包含被零除的常量确定。这会影响switch语句的语义、明确的赋值和取消赋值等。

错误号 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

错误号 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 的话说,这应该是编译时错误,而其他人则说不应该。似乎最终决定它既不是编译时错误也不是编译时常量。

于 2010-05-29T13:28:52.073 回答
3

好吧,如果您查看 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;

在 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 解析为“无穷大”并且与标准双精度数(或浮点数,等等)不同。

我应该注意到,我一点也不知道它是如何或为什么以这种方式实现的。当我看到上面的输出时,对我来说这一切都像是黑魔法。

于 2010-05-29T10:18:17.663 回答
2

Java明确要求整数除以零来触发ArithmeticException. j不能省略对的分配,因为这会违反规范。

于 2010-05-29T06:30:51.287 回答
0

这是合法的,因为编译器不应该在编译时折叠常量表达式。

“智能”编译器可能会编译:

a = 1 + 2

作为

a = 3

但是没有什么说编译器必须这样做。除此之外,1/0 是一个合法的表达式,就像:

int a;
int b;

a = a/b;

是合法的表达方式。

在 RUNTIME 它抛出一个异常,但这是一个运行时错误是有原因的。

于 2010-05-29T06:27:38.547 回答
0

从编译的角度来看是合法的,但如果执行它会抛出异常!

原因...良好的编程必须允许灵活性,因此您键入的所有表达式和每个代码都是编译器的变量,因此在数学表达式 X/ Y中,编译器不关心 Y 变量值是否为 ( Y== 0)或编译器的任何其他数字,这是一个变量......如果编译器也必须查看值,那将被视为运行时,不是吗。

于 2010-05-29T06:39:37.603 回答
0

当您无论如何都需要运行时变体时,为什么还要在编译时捕获这个?

例如,如果您从文本文件加载和解析“0”,然后尝试除以它,Java 将不知道您在编译时在做什么,因为它不知道该外部文件的内容。

此外,如果您要将任何变量设置为 0 并除以该变量,Java 必须在脚本中的每个点跟踪每个变量的每个可能值,以便在编译时捕获除以 0。

不妨保持一致并使其成为仅运行时异常。

于 2010-05-29T06:42:14.643 回答
0

既然其他人已经回答了 的合法性1/0,那么让我们转向第二个问题:

  • 如果这是合法的,有充分的理由吗?
    • 这可能有什么好处?

答案可能是:

取笑你的同事。;o)

当同事在他的计算机未锁定的情况下离开房间时,偷偷溜过并潜入某个在应用程序早期使用的某个类的静态初始化程序1/0的深处。这样,他会在应用程序部署之后(甚至在部署期间)通过遇到异常情况尽快发现,他可能会摸不着头脑。使用这种快速失败的方式,您可以确保这是一个相对无害的笑话。ArithmeticException

PS:它奏效了。;o)

于 2016-01-08T09:38:48.493 回答