Look at the following infinite while
loop in Java. It causes a compile-time error for the statement below it.
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
The following same infinite while
loop, however works fine and doesn't issue any errors in which I just replaced the condition with a boolean variable.
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
In the second case also, the statement after the loop is obviously unreachable because the boolean variable b
is true still the compiler doesn't complain at all. Why?
Edit : The following version of while
gets stuck into an infinite loop as obvious but issues no compiler errors for the statement below it even though the if
condition within the loop is always false
and consequently, the loop can never return and can be determined by the compiler at the compile-time itself.
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.
Edit : Same thing with if
and while
.
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.
}
The following version of while
also gets stuck into an infinite loop.
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
This is because the finally
block is always executed even though the return
statement encounters before it within the try
block itself.
I'm surprised your compiler refused to compile the first case. That seems strange to me.
But the second case isn't optimized to the first case because (a) another thread might update the value of
b
(b) the called function might modify the value ofb
as a side effect.Compilers aren't perfect - nor should they be
The responsibility of the compiler is to confirm syntax - not to confirm execution. Compilers can ultimately catch and prevent many run time problems in a strongly typed language - but they cannot catch all such errors.
The practical solution is to have batteries of unit tests to complement your compilers checks OR use object oriented components for implementing logic that are known to be robust, rather then relying on primitive variables and stop conditions.
Strong Typing and OO : increasing compiler's efficacy
Some errors are syntactical in nature - and in Java, the strong typing makes a lot of run time exceptions catchable. But, by using better types, you can help your compiler to enforce better logic.
If you want the compiler to enforce logic more effectively, in Java, the solution is to build robust, required objects that can enforce such logic, and using those objects to build up your application, rather than primitives.
A classic example of this is the use of the iterator pattern, combined with Java's foreach loop this construct is less vulnerable to the type of bug you illustrate than a simplistic while loop.
Actually I don't think anyone got it QUITE right (at least not in the original questioner's sense). The OQ keeps mentioning:
But it doesn't matter because the last line IS reachable. If you took that code, compiled it into a class file and handed the class file to someone else (say as a library), they could link the compiled class with code that modifies "b" through reflection, exiting the loop and causing the last line to execute.
This is true of any variable that isn't a constant (or final which compiles to a constant in the location where it's used--sometimes causing bizarre errors if you recompile the class with the final and not a class that references it, the referencing class will still hold the old value without any errors whatsoever)
I've used the ability of reflection to modify non-final private variables of another class to monkey-patch a class in a purchased library--fixing a bug so we could continue developing while we waited for official patches from the vendor.
By the way, this may not actually work these days--although I've done it before, there is a chance that such a small loop will be cached in the CPU cache and since the variable is not marked volatile the cached code may never pick up the new value. I've never seen this in action but I believe it's theoretically true.