以下编译在我的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
}
正如您所看到的,i
和k
赋值被优化为编译时常量,但除以0
(必须在编译时可检测到)简单地按原样编译。
javac 1.6.0_17
表现得更奇怪,无声地编译,但完全从字节码中删除i
和k
的赋值(可能是因为它确定它们没有在任何地方使用)但是保持1/0
完整(因为删除它会导致完全不同的程序语义)。
所以问题是:
1/0
实际上是一个合法的Java表达式,应该随时随地编译吗?
答案 0 :(得分:32)
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编译器发出警告是合理的。)
答案 1 :(得分:20)
我做了一些挖掘Bug数据库,并发现了一些有趣的信息。
以下代码是非法的:
class X { static final int i = 1 / 0; }
此编译时常量的值未定义,因此 必须是编译时错误。 Guy Steele证实了大约18个月 之前,这确实是预期的行为。
编译时常量必须静态提供其值(即 什么使它成为编译时常量;-)例如,其他的值 常量,其值由包含除法的常量确定 零是未定义的。这会影响
switch
语句的语义, 明确的分配和取消分配等。
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
Java编译器在尝试编译下一个测试时崩溃。此测试还会崩溃所有1.2beta4编译器版本,但12.beta3中缺少bug。示例和编译器诊断如下:
public class B { public static void main(String argv[]) { switch(0){ case 0/0: } } }
评估:编译器用于报告所有尝试除以常数零作为编译时错误。这在beta3中是固定的,因此代码将通过常数零生成。不幸的是,这个bug被引入。编译器应该优雅地处理case表达式中的除零。
所以1/0
是否应该编译的问题是一个有争议的讨论话题,有些人引用Guy Steele声称这应该是编译时错误,而其他人则说它不应该。似乎最终决定它既不是编译时错误也不是编译时常量。
答案 2 :(得分:2)
Java explicitly requires整数除以零以触发ArithmeticException
。无法省略对j
的分配,因为这会违反规范。
答案 3 :(得分:2)
好吧,如果您查看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解析为“无穷大”,并且与标准双打(或浮点数等等)处理不同。
我应该注意到,我没有丝毫知道如何或为什么以这种方式实施它。当我看到上面的输出时,对我来说这似乎都是黑魔法。
答案 4 :(得分:0)
这是合法的,因为编译器应该在编译时折叠常量表达式的地方没有。
“智能”编译器可能会编译:
a = 1 + 2
as
a = 3
但是没有任何东西可以说编译器可以做到这一点。除此之外,1/0是一个合法的表达式,如:
int a;
int b;
a = a/b;
是一种法律表达。
在RUNTIME,它会抛出异常,但出于某种原因,这是一个运行时错误。
答案 5 :(得分:0)
在编译观点中是合法的,但如果执行则会抛出异常!
原因......编程必须允许灵活性因此所有表达式和您键入的每个代码都是编译器的变量,因此在数学表达式X / Y 中编译器不关心如果Y变量值是( Y == 0 )或编译器的任何其他数字,这是一个变量...如果编译器也必须查看值,那么这将被视为运行时,不会。
答案 6 :(得分:0)
为什么还要在编译时抓住这个,当你还需要一个运行时变体时呢?
例如,如果你从文本文件加载并解析“0”,然后尝试除以它,Java就不知道你在编译时做了什么,因为它不知道它的内容外部文件。
此外,如果要将任何变量设置为0并除以变量,Java必须跟踪脚本中每个点的每个变量的每个可能值,以便在编译时捕获除以0。
不妨保持一致,并使其成为仅限运行时的例外。
答案 7 :(得分:0)
由于其他人已经回答了1/0
的合法性,让我们转到第二个问题:
答案可能是:
取笑你的同事。; o)
当同事离开房间时,他的计算机处于解锁状态,偷偷摸摸地将
1/0
深埋在应用程序早期使用的某个类的静态初始化程序中。通过这种方式,他可以在应用程序部署之后(甚至在部署期间)通过遇到不寻常的ArithmeticException
很快就会发现,他可能会在一段时间内抓住他的头。使用这种快速失败的方式,您可以确保它是一个相对无害的笑话。
P.S。:有效。 ; O)