Mira el siguiente while
bucle infinito en Java. Provoca un error en tiempo de compilación para la declaración que se encuentra debajo.
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
while
Sin embargo, el siguiente mismo bucle infinito funciona bien y no emite ningún error en el que acabo de reemplazar la condición con una variable booleana.
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
En el segundo caso también, la declaración después del ciclo es obviamente inalcanzable porque la variable booleana b
es verdadera, pero el compilador no se queja en absoluto. ¿Por qué?
Editar: la siguiente versión de while
se atasca en un bucle infinito como obvio, pero no emite errores de compilador para la declaración que se encuentra debajo, aunque la if
condición dentro del bucle es siempre false
y, en consecuencia, el bucle nunca puede regresar y puede ser determinado por el compilador en el el propio tiempo de compilación.
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.
Editar: Lo mismo con if
y 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.
}
La siguiente versión de while
también se atasca en un bucle infinito.
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
Esto se debe a que el finally
bloque siempre se ejecuta aunque la return
instrucción se encuentre antes que él dentro del try
bloque mismo.
fuente
Respuestas:
El compilador puede demostrar fácil e inequívocamente que la primera expresión siempre da como resultado un bucle infinito, pero no es tan fácil para la segunda. En su ejemplo de juguete es simple, pero ¿y si:
El compilador claramente no está buscando su caso más simple porque está renunciando a ese camino por completo. ¿Por qué? Porque es
mucho más difícil deprohibir según la especificación. Ver sección 14.21 :(Por cierto, mi compilador se queja cuando se declara la variable
final
).fuente
Según las especificaciones , se dice lo siguiente sobre las declaraciones while.
Por lo tanto, el compilador solo dirá que el código que sigue a una instrucción while es inalcanzable si la condición while es una constante con un valor verdadero, o si hay una instrucción break dentro de la instrucción while. En el segundo caso, dado que el valor de b no es una constante, no considera que el código que le sigue sea inalcanzable. Hay mucha más información detrás de ese enlace para brindarle más detalles sobre qué se considera inalcanzable y qué no.
fuente
Porque verdadero es constante y b se puede cambiar en el ciclo.
fuente
Debido a que analizar el estado de la variable es difícil, el compilador prácticamente se ha rendido y le permite hacer lo que desee. Además, la Especificación del lenguaje Java tiene reglas claras sobre cómo el compilador puede detectar código inalcanzable .
Hay muchas formas de engañar al compilador; otro ejemplo común es
public void test() { return; System.out.println("Hello"); }
que no funcionaría, ya que el compilador se daría cuenta de que el área era inaccesible. En cambio, podrías hacer
public void test() { if (2 > 1) return; System.out.println("Hello"); }
Esto funcionaría ya que el compilador no puede darse cuenta de que la expresión nunca será falsa.
fuente
if
es un poco diferente de awhile
, porque el compilador compilará incluso la secuenciaif(true) return; System.out.println("Hello");
sin quejarse. IIRC, esta es una excepción especial en el JLS. El compilador podría detectar el código inalcanzable después deif
tan fácil como conwhile
.Este último no es inalcanzable. El booleano b todavía tiene la posibilidad de ser alterado a falso en algún lugar dentro del bucle y causar una condición final.
fuente
Mi conjetura es que la variable "b" tiene la posibilidad de cambiar su valor, por lo que
System.out.println("while terminated");
se puede llegar al pensamiento del compilador .fuente
Los compiladores no son perfectos, ni deberían serlo
La responsabilidad del compilador es confirmar la sintaxis, no confirmar la ejecución. En última instancia, los compiladores pueden detectar y prevenir muchos problemas de tiempo de ejecución en un lenguaje fuertemente tipado, pero no pueden detectar todos esos errores.
La solución práctica es tener baterías de pruebas unitarias para complementar las comprobaciones de los compiladores O utilizar componentes orientados a objetos para implementar la lógica que se sabe que es robusta, en lugar de depender de variables primitivas y condiciones de parada.
Strong Typing y OO: aumentando la eficacia del compilador
Algunos errores son de naturaleza sintáctica, y en Java, el tipo fuerte hace que se puedan detectar muchas excepciones en tiempo de ejecución. Pero, al usar mejores tipos, puede ayudar a su compilador a aplicar una mejor lógica.
Si desea que el compilador aplique la lógica de manera más efectiva, en Java, la solución es construir objetos robustos y requeridos que puedan hacer cumplir dicha lógica y usar esos objetos para construir su aplicación, en lugar de primitivas.
Un ejemplo clásico de esto es el uso del patrón de iterador, combinado con el bucle foreach de Java, esta construcción es menos vulnerable al tipo de error que ilustra que un bucle while simplista.
fuente
El compilador no es lo suficientemente sofisticado para ejecutar los valores que
b
pueden contener (aunque solo lo asigna una vez). El primer ejemplo es fácil de ver para el compilador que será un bucle infinito porque la condición no es variable.fuente
Me sorprende que su compilador se haya negado a compilar el primer caso. Eso me parece extraño.
Pero el segundo caso no está optimizado para el primer caso porque (a) otro hilo podría actualizar el valor de
b
(b) la función llamada podría modificar el valor deb
como un efecto secundario.fuente
En realidad, no creo que nadie lo haya entendido MUY bien (al menos no en el sentido del interrogador original). La OQ sigue mencionando:
Pero no importa porque la última línea ES accesible. Si tomó ese código, lo compiló en un archivo de clase y le entregó el archivo de clase a otra persona (por ejemplo, como una biblioteca), podría vincular la clase compilada con código que modifica "b" a través de la reflexión, saliendo del bucle y provocando el último línea para ejecutar.
Esto es cierto para cualquier variable que no sea una constante (o final que se compile en una constante en la ubicación donde se usa, lo que a veces causa errores extraños si vuelve a compilar la clase con el final y no con una clase que hace referencia a ella, la referencia la clase aún mantendrá el valor anterior sin ningún error)
He usado la capacidad de reflexión para modificar variables privadas no finales de otra clase para parchear una clase en una biblioteca comprada, arreglando un error para que pudiéramos continuar desarrollando mientras esperábamos los parches oficiales del proveedor.
Por cierto, es posible que esto no funcione en estos días; aunque lo he hecho antes, existe la posibilidad de que un bucle tan pequeño se almacene en caché en la caché de la CPU y, dado que la variable no está marcada como volátil, es posible que el código en caché nunca recoger el nuevo valor. Nunca he visto esto en acción, pero creo que es teóricamente cierto.
fuente
b
es una variable de método. ¿Realmente puedes modificar una variable de método usando la reflexión? Independientemente, el punto general es que no debemos asumir que la última línea no es accesible.Es simplemente porque el compilador no hace demasiado trabajo de niñera, aunque es posible.
El ejemplo que se muestra es simple y razonable para que el compilador detecte el bucle infinito. Pero, ¿qué tal si insertamos 1000 líneas de código sin ninguna relación con la variable
b
? ¿Y qué tal esas declaraciones son todasb = true;
? El compilador definitivamente puede evaluar el resultado y decirle que es cierto eventualmente enwhile
bucle, pero ¿qué tan lento será compilar un proyecto real?PD: la herramienta Lint definitivamente debería hacerlo por ti.
fuente
Desde la perspectiva del compilador
b
enwhile(b)
podría cambiar a falso en alguna parte. El compilador simplemente no se molesta en verificar.Por diversión
while(1 < 2)
, prueba ,for(int i = 0; i < 1; i--)
etc.fuente
Las expresiones se evalúan en tiempo de ejecución, por lo que, al reemplazar el valor escalar "verdadero" con algo como una variable booleana, cambia un valor escalar en una expresión booleana y, por lo tanto, el compilador no tiene forma de saberlo en el momento de la compilación.
fuente
Si el compilador puede determinar de manera concluyente que el booleano evaluará a
true
en tiempo de ejecución, arrojará ese error. El compilador asume que la variable que declaró se puede cambiar (aunque sabemos que aquí como humanos no lo hará).Para enfatizar este hecho, si las variables se declaran como
final
en Java, la mayoría de los compiladores arrojarán el mismo error que si sustituyera el valor. Esto se debe a que la variable se define en tiempo de compilación (y no se puede cambiar en tiempo de ejecución) y, por tanto, el compilador puede determinar de manera concluyente que la expresión se evalúatrue
en tiempo de ejecución.fuente
La primera instrucción siempre da como resultado un bucle infinito porque hemos especificado una constante en la condición de bucle while, donde, como en el segundo caso, el compilador asume que existe la posibilidad de un cambio de valor de b dentro del bucle.
fuente