1/0是合法的Java表达式吗?

pol*_*nts 37 java divide-by-zero

以下在我的Eclipse中编译得很好:

final int j = 1/0;
// compiles fine!!!
// throws ArithmeticException: / by zero at run-time
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在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

}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这些ik赋值被优化为编译时常量,但是除以0(必须在编译时可以检测到)除以简单编译.

javac 1.6.0_17表现得更加奇怪,无声地编译,但是将字符编码分配到字节码ik完全从字节码中删除(可能是因为它确定它们没有在任何地方使用)但保留1/0完整(因为删除它会导致完全不同的程序语义).

所以问题是:

  • 1/0实际上应该编译随时随地法律Java表达式?
    • JLS怎么说呢?
  • 如果这是合法的,有充分的理由吗?
    • 这有什么用呢?

Ste*_*n C 32

1/0实际上应该编译随时随地法律Java表达式?

是.

JLS怎么说呢?

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

"编译时常量表达式是表示基本类型的值的表达式或不突然完成的字符串,仅使用以下内容组成:..."

(重点补充.)所以以下内容不会编译:

switch(i) {
    case 1:
    case 1 + 1: 
    case 1 / 0:  // compilation error.
}
Run Code Online (Sandbox Code Playgroud)

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

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

但这完全是由所有.事实是,这1/0是有效的Java代码,并且任何Java编译器都不应将此标记为编译错误.(如果有一个编译器开关将其关闭,那么Java编译器发出警告是合理的.)

  • 考虑到我击中NPE与ArithmeticException的次数,这是非常合理的. (3认同)
  • @Pete - 我可以接受,虽然有这种警告的先例.例如,如果Eclipse Java编译器发现一个语句将始终抛出NPE,它将发出警告. (2认同)
  • 字节码不包含switch语句,因此需要在编译时进行评估.无法计算除以零的值,因此您会收到编译错误(编译器实际上是运行它来评估它). (2认同)

pol*_*nts 20

我做了一些挖掘Bug数据库,并发现了一些有趣的信息.

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

以下代码是非法的:

class X { static final int i = 1 / 0; }
Run Code Online (Sandbox Code Playgroud)

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

编译时常量必须静态地使其值可用(这使得它成为编译时常量;-)例如,其值由包含除以零的常量确定的其他常量的值是未定义的.这会影响switch陈述的语义,明确的分配和取消分配等.

错误ID 4089107:javac将整数除以(常数)零视为错误

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

运行以上产量:

zero.java:3: Arithmetic exception.
     System.out.println(1/0);
                         ^
1 error
Run Code Online (Sandbox Code Playgroud)

错误ID 4154563:javac在case表达式中接受零除常量表达式.

Java编译器在尝试编译下一个测试时崩溃.此测试还会崩溃所有1.2beta4编译器版本,但12.beta3中缺少bug.示例和编译器诊断如下:

public class B {
   public static void main(String argv[]) {
      switch(0){
         case 0/0:
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

评估:编译器用于报告所有尝试除以常数零作为编译时错误.这是在beta3 中修复的,因此将生成代码以除以常数零.不幸的是,这个bug被引入.编译器应该优雅地处理case表达式中的除零.

结论

因此,是否1/0应该编译的问题是一个有争议的讨论话题,有些人引用Guy Steele声称这应该是编译时错误,而其他人则说它不应该.似乎最终决定它既不是编译时错误也不是编译时常量.