Java中的无限循环

在Java中查看下面的无限while循环。 它会导致它下面的语句编译时错误。

 while(true) { System.out.println("inside while"); } System.out.println("while terminated"); //Unreachable statement - compiler-error. 

以下相同的无限while循环,但工作正常,不会发出任何错误,我只是用布尔variablesreplace条件。

 boolean b=true; while(b) { System.out.println("inside while"); } System.out.println("while terminated"); //No error here. 

在第二种情况下,循环后面的语句显然是不可达的,因为布尔variablesb是真的,编译器根本不会抱怨。 为什么?


编辑:下面的版本while明显陷入死循环中,但是对于它下面的语句没有编译器错误,即使循环中的if条件总是false ,因此循环永远不会返回,并且可以由编译器本身就是编译器。

 while(true) { if(false) { break; } System.out.println("inside while"); } System.out.println("while terminated"); //No error here. 

 while(true) { if(false) { //if true then also return; //Replacing return with break fixes the following error. } System.out.println("inside while"); } System.out.println("while terminated"); //Compiler-error - unreachable statement. 

 while(true) { if(true) { System.out.println("inside if"); return; } System.out.println("inside while"); //No error here. } System.out.println("while terminated"); //Compiler-error - unreachable statement. 

编辑:同样的事情, ifwhile

 if(false) { System.out.println("inside if"); //No error here. } 

 while(false) { System.out.println("inside while"); // Compiler's complain - unreachable statement. } 

 while(true) { if(true) { System.out.println("inside if"); break; } System.out.println("inside while"); //No error here. } 

以下版本也陷入了无限循环。

 while(true) { try { System.out.println("inside while"); return; //Replacing return with break makes no difference here. } finally { continue; } } 

这是因为即使return语句在try块本身之前遇到finally块, finally块也总是被执行。

编译器可以很容易和明确地certificate第一个expression式总是导致一个无限循环,但是对于第二个expression式来说并不那么容易。 在你的玩具的例子很简单,但如果:

  • variables的内容是从文件中读取的?
  • 该variables不是本地的,可以被另一个线程修改?
  • variables依赖于一些用户input?

编译器显然不检查你的简单情况,因为它完全放弃了这条路。 为什么? 因为规范要严格得多 。 参见14.21节 :

(顺便说一下,当variables被声明为final时,我的编译器报错。)

根据规格 ,以下是关于发言的说法。

如果满足以下至less一个条件,while语句可以正常完成:

  • while语句是可达的,条件expression式不是一个值为true的常量expression式。
  • 有一个可访问的break语句退出while语句。\

因此,如果while条件是一个具有真值的常量,或者在while语句中有break语句,编译器只会说while后面的代码是不可访问的。 在第二种情况下,由于b的值不是一个常数,所以它不考虑它后面的代码是不可达的。 这个链接背后有更多的信息,让你知道什么是什么,什么是不被认为是不可达的。

因为真是恒定的,b可以在循环中改变。

因为分析variables状态很难,所以编译器几乎已经放弃了,让你做你想做的事情。 另外,Java语言规范对如何允许编译器检测不可达代码有明确的规则。

欺骗编译器有很多方法 – 另一个常见的例子是

 public void test() { return; System.out.println("Hello"); } 

这是行不通的,因为编译器会意识到该地区是不可能的。 相反,你可以这样做

 public void test() { if (2 > 1) return; System.out.println("Hello"); } 

这是可行的,因为编译器不能意识到expression式永远不会是错误的。

后者是不可达的。 布尔值b仍然有可能被修改为false,导致结束条件的循环内的某个地方。

我的猜测是variables“b”有可能改变它的值,所以编译器认为System.out.println("while terminated"); 可以达到。

编译器并不完美 – 也不应该这样做

编译器的责任是确认语法 – 不是要确认执行。 编译器可以最终捕获和防止强types语言中的许多运行时问题 – 但是它们不能捕获所有这些错误。

实际的解决scheme是使用unit testing的电池来补充编译器的检查,或者使用面向对象的组件来实现已知稳健的逻辑,而不是依靠原始variables和停止条件。

强大的打字和OO:提高编译器的功效

有些错误本质上是语法的 – 在Java中,强types使得运行时exception可以被捕获。 但是,通过使用更好的types,可以帮助编译器执行更好的逻辑。

如果您希望编译器更有效地执行逻辑,那么在Java中,解决scheme就是构build可强制执行此类逻辑的强健的必需对象,并使用这些对象构build应用程序,而不是构build原语。

一个经典的例子是迭代器模式的使用,加上Java的foreach循环,这个构造对于你所说明的缺陷types的影响要小于一个简单的while循环。

编译器不够复杂,无法运行b可能包含的值(尽pipe您只分配一次)。 第一个例子很容易让编译器看到它将是一个无限循环,因为条件是不可变的。

我很惊讶你的编译器拒绝编译第一个案例。 这对我来说似乎很奇怪。

但是第二种情况没有针对第一种情况进行优化,因为(a)另一个线程可能更新b的值(b)被调用的函数可能会修改b的值作为副作用。

其实我觉得没有人知道它是正确的(至less不是在原来的提问者的意义上)。 OQ不断提及:

正确,但不相关,因为b在循环中没有被改变

但是没关系,因为最后一行是可达的。 如果你把这个代码编译成一个类文件并把类文件交给其他人(比如库),那么他们可以把编译后的类与通过reflection修改“b”的代码联系起来,退出循环并导致最后一个行执行。

任何不是常量的variables(或最终编译为使用位置的常量)都是如此 – 如果使用final来重新编译类,而不是引用它的类,则引用奇怪的错误,引用class级仍旧保持原来的价值,没有任何错误)

我已经使用了reflection的能力来修改另一个类的非最终私有variables,以便在购买的库中修改一个类 – 修复一个bug,以便在我们等待来自供应商的官方补丁时继续开发。

顺便说一下,这可能实际上不是现在的工作 – 虽然我以前做过,但有一个机会,这样一个小的循环将caching在CPUcaching中,并且由于该variables不标记为volatile,caching的代码可能永远不会拿起新的价值。 我从来没有见过这样的行动,但我相信这在理论上是正确的。

这只是因为编译器不要太多的宝宝坐着工作,尽pipe这是可能的。

所示的例子对于编译器检测无限循环是简单而合理的。 但是我们如何插入1000行代码,而与variablesb没有任何关系呢? 那么这些陈述如何都是b = true; ? 编译器肯定可以评估结果,并告诉你最终在while循环中是真的,但编译一个真正的项目会有多慢?

PS,皮棉工具绝对应该为你做。

从编译器的angular度来看, while(b)可能在某处变成false。 编译器只是不麻烦检查。

为了有趣的尝试while(1 < 2)for(int i = 0; i < 1; i--)

在运行时计算expression式,所以当用一个布尔variablesreplace标量值“true”时,您将标量值更改为布尔expression式,因此编译器无法在编译时知道它。

如果编译器可以确定地确定布尔值在运行时将评估为true ,则会抛出该错误。 编译器假定你声明的variables是可以改变的(虽然我们在这里知道它不会)。

为了强调这个事实,如果variables在Java中被声明为final ,那么大多数编译器都会抛出相同的错误,就像replace值一样。 这是因为variables是在编译时定义的(并且不能在运行时更改),因此编译器可以确定在运行时expression式的计算结果为true

第一个Statement总是导致一个无限循环,因为我们已经在while循环的条件中指定了一个常量,而在第二个情况下,编译器假设在循环内部有可能改变b的值。