【问题标题】:Is 1/0 a legal Java expression?1/0 是合法的 Java 表达式吗?
【发布时间】:2010-05-29 06:22:37
【问题描述】:

以下在我的 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中编译,上面的sn -p生成如下字节码(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 对此有何评论?
  • 如果这是合法的,是否有充分的理由?
    • 这可能有什么好处?

【问题讨论】:

  • Downvoter:想解释一下这个问题如何不清楚或没有用处?因为我以后会有更多的问题,所以我想知道在哪里可以改进。
  • @polygenelubricants:老实说,我不知道这些细节是否会让你成为更好的程序员。 25 年以上的编程经验,除了来自 “语言律师” POV 和“Java Puzzlers” POV(尽管 Java 谜题者比这更好),我认为这有一个非零概率只是头发分裂和浪费时间(在这里说实话:大多数在这里回答的专业开发人员可能都不知道,而且在他们不知道这个 Java 细节的情况下,这可能不会以任何方式伤害他们)。我,一方面,不想看到这样的问题出没:(
  • 另一种说法是:对我来说,一直花时间挑剔/回答/评论这样的问题(显然你将来会有更多类似的问题)可以花在学习上,比如说,一点点 Lisp ……那真的会让你成为一个更好的程序员。
  • @Webinator 另一个观点,来自另一个几十年的程序员。很久以前,一个我非常尊敬的人告诉我,如果我不知道至少三种破坏工具的方法,我还没有真正学会使用它。当我开始编程时,我将这个比喻扩展到适用于边缘条件、奇怪的行为,甚至是一些吹毛求疵的事情。编程就是管理细节。
  • @Webinator:比起软件工程师,我更像是一名计算机科学家,所以像这样的语言问题让我非常感兴趣。我不仅关心如何编写更好的程序。我还关心如何设计更好的语言。

标签: java divide-by-zero


【解决方案1】:

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 编译器发出警告是合理的。)

【讨论】:

  • 没有警告是合理的,因为除以计算为零的编译时间常数不是一个常见的错误原因,浪费开发时间检测。
  • @Pete - 我可以接受这一点,尽管这种警告有先例。例如,如果 Eclipse Java 编译器发现一条语句总是会抛出 NPE,它将发出警告。
  • 考虑到我遇到 NPE 和 ArithmeticException 的次数,这是非常合理的。
  • @Pete:编译器必须检查表达式是否抛出异常,因为在这种情况下它不被视为编译时常量。所以在这种情况下发出警告不会有太多开销。
  • 字节码不包含switch语句,因此需要在编译时进行求值。一除以零是无法计算出来的,所以你会得到一个编译错误(编译器实际上是在运行它来评估它)。
【解决方案2】:

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

Bug ID 4178182: JLS doesnt specify behavior for 1/0 as a constant expression

以下代码是非法的:

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

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

编译时常量必须具有静态可用的值(即 是什么使它成为编译时常量 ;-) 例如,其他的值 其值由包含除法的常量确定的常量 零是未定义的。这会影响switch 语句的语义, 明确的分配和取消分配等。

Bug ID 4089107: javac treats integer division by (constant) zero as an error

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

Bug ID 4154563: javac accepts division by zero constant expressions in case expressions.

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

【讨论】:

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

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

    【讨论】:

    • 浮点类型(doublefloat)与整数的工作方式不同。 FP 类型有一种表示无穷大的方法,而整数类型没有这种方法。最初的问题是关于整数的,因此关于 FP 类型的讨论并不真正相关。
    • 问题是被零除是否“应该随时随地编译?”。提供的示例确实使用了整数,但从未明确询问整数是否可以保存除以零的值,除非它在语言中是合法的。那么,答案是肯定的。
    • 该问题明确询问表达式 1/0,而不是表达式 1.0/0.0,所以我仍然同意 Jesper 的评论。 Imo,很明显,这是一个讨论是否,如果是,为什么,1/0 应该是编译时错误而不是运行时异常,这两者都不适合浮点类型。
    【解决方案4】:

    Java explicitly requires 整数除以零触发ArithmeticException。不能省略对 j 的分配,因为这会违反规范。

    【讨论】:

    • 如果能编译,肯定不能省略;这是众所周知的,也是公认的。问题是它是否应该首先编译。
    【解决方案5】:

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

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

    a = 1 + 2
    

    作为

    a = 3
    

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

    int a;
    int b;
    
    a = a/b;
    

    是一个合法的表达方式。

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

    【讨论】:

    • int a; int b; a = a/b; 通过 javac 抛出编译时异常。
    • @Bart K. - 没有“编译时抛出的异常”之类的东西。编译器确实会给你一个错误。异常只在运行时抛出。
    • sigh 表达式合法,编译器错误是由于未初始化的变量。很惊讶我没有因为在第一个表达式中缺少分号而被否决。
    • @Will,我没有说这个表达是合法的。你说“在运行时它会抛出一个异常”这不是真的:int a; int b; a = a/b; 不会到达运行时。
    • @Bart 但是你很迂腐,期望仅仅为了宣传故事而编写的代码应该是合法且完美的代码。代码的意图很明确,即使它是不完美的 Java,因为 a 和 b 没有被初始化。我输入它们以确保有人不会说“如果 b 是字符串怎么办”或其他一些废话。
    【解决方案6】:

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

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

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

    最好让事情保持一致并使其成为仅运行时异常。

    【讨论】:

    • 如果String s = (String) Integer.valueOf(0); // doesn't compile! 会在运行时抛出ClassCastException,为什么还要在编译时捕获它?
    • 因为 Integer.valueOf(0) 是一个类型问题(当它期待一个字符串时是整数参数),并且由于 Java 的严格类型,在编译时检测到类型问题。除以零是一个计算问题,Java 不会在编译时执行计算,因为在许多情况下这是不可能的(例如,对从数据库/外部文件加载的变量进行计算)。因此,所有计算问题都会在运行时捕获。
    【解决方案7】:

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

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

    【讨论】:

    • 我不认为检查除以零会大大增加编译时间。
    • 如果“零”是一个变量。然后,Java 必须在除法语句之前执行涉及该变量的所有计算,以确定该变量在该点是否为零,并且在许多情况下这是不可能的(例如,从外部源加载的变量)。如果它不是变量,则意味着您实际编码: a = b/0;那将是一个非常业余的错误,一个人肯定只能在受到影响时犯:P
    • 从未说过会增加编译时间。我想说的是编译器不会在编译时评估表达式,但它确实确定了除法中变量(int/float/...)的类型及其值,并将类型和值保存在class 文件作为字节码,它不评估表达式。因为评估所有可能的表达式将是无止境且毫无意义的。
    • @OliverWeiler - (编译时)性能成本无关紧要。相关的一点是编译器是否允许实现该检查并将其称为编译错误。除非语言规范允许,否则不允许编译器做这样的聪明事情。在这种情况下,JLS 不允许这样做。 (智能编译器会做 JLS 不认可的有用事情,这是一件坏事,因为它们会导致源代码可移植性问题。)
    【解决方案8】:

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

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

    答案可能是:

    取笑你的同事。 ;o)

    当同事离开房间,他的电脑没有上锁时,偷偷溜过1/0 并将其埋入某个应用程序早期使用的某个类的静态初始化器 深处。这样,他会在部署应用程序之后(甚至在部署期间)通过遇到不寻常的ArithmeticException 很快发现,他可能会摸不着头脑。使用这种快速失败的方法,您可以确保这是一个相对无害的笑话。

    P.S.:它奏效了。 ;o)

    【讨论】:

      猜你喜欢
      • 2015-09-21
      • 2012-12-16
      • 1970-01-01
      • 2017-01-09
      • 1970-01-01
      • 1970-01-01
      • 2016-01-03
      • 2013-05-31
      • 1970-01-01
      相关资源
      最近更新 更多