Bucles infinitos en Java

82

Mira el siguiente whilebucle 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.

whileSin 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 bes verdadera, pero el compilador no se queja en absoluto. ¿Por qué?


Editar: la siguiente versión de whilese atasca en un bucle infinito como obvio, pero no emite errores de compilador para la declaración que se encuentra debajo, aunque la ifcondición dentro del bucle es siempre falsey, 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 ify 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 whiletambié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 finallybloque siempre se ejecuta aunque la returninstrucción se encuentre antes que él dentro del trybloque mismo.

León
fuente
46
¿A quien le importa? Obviamente, es solo una característica del compilador. No se preocupe por este tipo de cosas.
CJ7
17
¿Cómo podría otro hilo cambiar una variable local no estática?
CJ7
4
El estado interno del objeto puede cambiarse al mismo tiempo mediante la reflexión. Es por eso que JLS exige que solo se verifiquen las expresiones finales (constantes).
lsoliveira
5
Odio estos estúpidos errores. El código inalcanzable debe ser una advertencia, no un error.
user606723
2
@ CJ7: Yo no llamaría a esto una "característica", y hace que sea muy tedioso (sin ninguna razón) implementar un compilador Java conforme. Disfrute de la dependencia de su proveedor por diseño.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

Respuestas:

105

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 contenido de la variable se leyó de un archivo?
  • la variable no era local y podría ser modificada por otro hilo?
  • la variable se basó en alguna entrada del usuario?

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 de prohibir según la especificación. Ver sección 14.21 :

(Por cierto, mi compilador se queja cuando se declara la variable final).

Wayne
fuente
25
-1 - No se trata de lo que puede hacer el compilador. No se trata de que los controles sean más fáciles o más difíciles. Se trata de lo que el compilador puede hacer ... según las especificaciones del lenguaje Java. La segunda versión del código es Java válido según JLS, por lo que un error de compilación sería incorrecto.
Stephen C
10
@StephenC - Gracias por esa información. Felizmente actualizado para reflejar tanto.
Wayne
Aún -1; ninguno de sus "qué pasaría si" se aplica aquí. Tal como está, el compilador puede resolver el problema; en términos de análisis estático, esto se denomina propagación constante y se usa ampliamente con éxito en muchas otras situaciones. El JLS es la única razón aquí, y la dificultad de resolver el problema es irrelevante. Por supuesto, la razón por la que el JLS está escrito de esa manera en primer lugar podría estar relacionado con la dificultad de ese problema, aunque personalmente sospecho que en realidad es porque es difícil hacer cumplir una solución estandarizada de propagación constante en diferentes compiladores.
Roble
2
@Oak: admití en mi respuesta que "En el ejemplo del juguete [del OP] es simple" y, según el comentario de Stephen, que el JLS es el factor limitante. Estoy bastante seguro de que estamos de acuerdo.
Wayne
55

Según las especificaciones , se dice lo siguiente sobre las declaraciones while.

Una sentencia while puede completarse normalmente si al menos uno de los siguientes es verdadero:

  • La instrucción while es accesible y la expresión de condición no es una expresión constante con valor verdadero.
  • Hay una instrucción break accesible que sale de la instrucción 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.

Kibbee
fuente
3
+1 por señalar que no es solo lo que los escritores del compilador pueden o no pueden pensar, el JLS les dice lo que pueden y no pueden considerar inalcanzable.
yshavit
14

Porque verdadero es constante y b se puede cambiar en el ciclo.

hope_is_grim
fuente
1
Correcto, pero irrelevante, ya que b NO se cambia en el ciclo. También se podría argumentar que el bucle que usa true podría incluir una declaración de ruptura.
desparasitación
@deworde: siempre que exista la posibilidad de que pueda ser cambiado (por otro hilo, digamos), entonces el compilador no puede llamarlo un error. No se puede saber con solo mirar el bucle en sí mismo si se cambiará o no b mientras se ejecuta el bucle.
Peter Recore
10

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.

kba
fuente
6
Como han dicho otras respuestas, esto no está (directamente) relacionado con lo que podría ser fácil o difícil para el compilador detectar el código inalcanzable. El JLS hace todo lo posible para especificar las reglas para detectar declaraciones inalcanzables. Según esas reglas, el primer ejemplo no es Java válido y el segundo ejemplo es Java válido. Eso es. El compilador simplemente implementa las reglas como se especifica.
Stephen C
No sigo el argumento de JLS. ¿Están los compiladores realmente obligados a convertir todo Java legal en código de bytes? incluso si se puede demostrar que no tiene sentido.
emory
@emory Sí, esa sería la definición de un navegador compatible con los estándares, que obedece al estándar y compila código Java legal. Y si usa un navegador que no cumple con los estándares, aunque puede tener algunas características interesantes, ahora está confiando en la teoría de un diseñador de compiladores ocupado sobre lo que es una regla "sensata". Lo que es un escenario realmente terrible: "¿Por qué alguien querría usar DOS condicionales en el mismo si? Yo nunca lo he hecho")
deworde
Este ejemplo de uso ifes un poco diferente de a while, porque el compilador compilará incluso la secuencia if(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 de iftan fácil como con while.
Christian Semrau
2
@emory: cuando alimenta un programa Java a través de un procesador de anotaciones de terceros, ya no es ... en un sentido muy real ... Java. Más bien, es Java superpuesto con las extensiones y modificaciones lingüísticas que implementa el procesador de anotaciones. Pero eso NO es lo que está pasando aquí. Las preguntas del OP son sobre Java vainilla, y las reglas de JLS gobiernan qué es y qué no es un programa Java vainilla válido.
Stephen C
6

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.

Cheesegraterr
fuente
Correcto, pero irrelevante, ya que b NO se cambia en el ciclo. También podría argumentar que el ciclo que usa true podría incluir una declaración de interrupción, lo que daría una condición final.
desparasitación
4

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 .

xuanyuanzhiyuan
fuente
4

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.

jayunit100
fuente
Tenga en cuenta que OO no es la única forma de ayudar a los mecanismos de escritura estáticos fuertes a encontrar errores. Los tipos paramétricos y las clases de tipos de Haskell (que son un poco diferentes de las llamadas clases en los lenguajes OO) son en realidad bastante mejores en esto.
izquierda rotonda alrededor del
3

El compilador no es lo suficientemente sofisticado para ejecutar los valores que bpueden 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.

Jonathan M
fuente
4
Esto no está relacionado con la "sofisticación" del compilador. El JLS hace todo lo posible para especificar las reglas para detectar declaraciones inalcanzables. Según esas reglas, el primer ejemplo no es Java válido y el segundo ejemplo es Java válido. Eso es. El escritor del compilador debe implementar las reglas como se especifica ... o de lo contrario su compilador no es conforme.
Stephen C
3

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 de bcomo un efecto secundario.

sarnold
fuente
2
Si está sorprendido, entonces no ha leído lo suficiente sobre las especificaciones del lenguaje Java :-)
Stephen C
1
Jaja :) Es bueno saber dónde estoy parado. ¡Gracias!
sarnold
3

En realidad, no creo que nadie lo haya entendido MUY bien (al menos no en el sentido del interrogador original). La OQ sigue mencionando:

Correcto, pero irrelevante, ya que b NO se cambia en el ciclo

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.

Bill K
fuente
Buen punto. Supongo que bes 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.
emory
Ouch, tienes razón, asumí que b era un miembro. Si b es una variable de método, entonces creo que el compilador "Puede" saber que no cambiará pero no lo hace (lo que hace que todas las demás respuestas sean MUY correctas después de todo)
Bill K
3

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 todas b = true;? El compilador definitivamente puede evaluar el resultado y decirle que es cierto eventualmente en whilebucle, pero ¿qué tan lento será compilar un proyecto real?

PD: la herramienta Lint definitivamente debería hacerlo por ti.

pinxue
fuente
2

Desde la perspectiva del compilador ben while(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.

Domingo Lunes
fuente
2

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.

Wilmer
fuente
2

Si el compilador puede determinar de manera concluyente que el booleano evaluará a trueen 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 finalen 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úa trueen tiempo de ejecución.

Cameron S
fuente
2

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.

Sheo
fuente