Método Java con compilaciones de tipo de retorno sin declaración de retorno

228

Pregunta 1:

¿Por qué se compila el siguiente código sin tener una declaración de retorno?

public int a() {
    while(true);
}

Aviso: si agrego return después de un tiempo, obtengo un Unreachable Code Error.

Pregunta 2:

Por otro lado, ¿por qué se compila el siguiente código?

public int a() {
    while(0 == 0);
}

aunque lo siguiente no.

public int a(int b) {
    while(b == b);
}
Willi Mentzel
fuente
2
No es un duplicado de stackoverflow.com/questions/16789832/… , gracias a la segunda mitad de la segunda pregunta.
TJ Crowder

Respuestas:

274

Pregunta 1:

¿Por qué se compila el siguiente código sin tener una declaración de retorno?

public int a() 
{
    while(true);
}

Esto está cubierto por JLS§8.4.7 :

Si se declara que un método tiene un tipo de retorno (§8.4.5), se produce un error en tiempo de compilación si el cuerpo del método puede completarse normalmente (§14.1).

En otras palabras, un método con un tipo de retorno debe regresar solo mediante el uso de una declaración de retorno que proporcione un valor de retorno; el método no puede "dejar el extremo de su cuerpo". Vea §14.17 para las reglas precisas sobre las declaraciones de retorno en un cuerpo de método.

Es posible que un método tenga un tipo de retorno y, sin embargo, no contenga declaraciones de retorno. Aquí hay un ejemplo:

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

Como el compilador sabe que el ciclo nunca terminará ( truesiempre es cierto, por supuesto), sabe que la función no puede "regresar normalmente" (dejar el extremo de su cuerpo), y por lo tanto está bien que no haya return.

Pregunta 2:

Por otro lado, ¿por qué se compila el siguiente código?

public int a() 
{
    while(0 == 0);
}

aunque lo siguiente no.

public int a(int b)
{
    while(b == b);
}

En el 0 == 0caso, el compilador sabe que el ciclo nunca terminará (eso 0 == 0siempre será cierto). Pero no lo sabe por b == b.

Por qué no?

El compilador comprende expresiones constantes (§15.28) . Citando §15.2 - Formas de expresiones (porque curiosamente esta oración no está en §15.28) :

Algunas expresiones tienen un valor que se puede determinar en tiempo de compilación. Estas son expresiones constantes (§15.28).

En su b == bejemplo, debido a que hay una variable involucrada, no es una expresión constante y no se especifica que se determine en el momento de la compilación. Podemos ver que siempre será cierto en este caso (aunque si bfuera un double, como señaló QBrute , podríamos engañarnos fácilmente Double.NaN, lo que no== es en sí mismo ), pero el JLS solo especifica que las expresiones constantes se determinan en el momento de la compilación , no permite que el compilador intente evaluar expresiones no constantes. bayou.io planteó un buen punto para por qué no: si comienza a tratar de determinar expresiones que involucran variables en el momento de la compilación, ¿dónde se detiene? b == bes obvio (er, para noNaNvalores), pero ¿qué pasa a + b == b + a? O (a + b) * 2 == a * 2 + b * 2? Dibujar la línea en las constantes tiene sentido.

Por lo tanto, dado que no "determina" la expresión, el compilador no sabe que el ciclo nunca terminará, por lo que cree que el método puede regresar normalmente, lo que no está permitido, porque es obligatorio return. Entonces se queja de la falta de a return.

TJ Crowder
fuente
34

Puede ser interesante pensar en un tipo de retorno de método no como una promesa de devolver un valor del tipo especificado, sino como una promesa de no devolver un valor que no sea del tipo especificado. Por lo tanto, si nunca devuelve nada, no está incumpliendo la promesa, por lo que cualquiera de los siguientes es legal:

  1. Bucle para siempre:

    X foo() {
        for (;;);
    }
  2. Recurrente para siempre:

    X foo() {
        return foo();
    }
  3. Tirando una excepción:

    X foo() {
        throw new Error();
    }

(Me parece divertido pensar en la recursividad: el compilador cree que el método devolverá un valor de tipo X(sea lo que sea), pero no es cierto, porque no hay código presente que tenga alguna idea de cómo crear o procurar un X.)

Boann
fuente
8

Si observa el código de bytes, si lo que se devuelve no coincide con la definición, recibirá un error de compilación.

Ejemplo:

for(;;) mostrará los bytecodes:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Tenga en cuenta la falta de código de bytes de retorno

Esto nunca llega a un retorno y, por lo tanto, no devuelve el tipo incorrecto.

A modo de comparación, un método como:

public String getBar() { 
    return bar; 
}

Devolverá los siguientes códigos de bytes:

public java.lang.String getBar();
    Code:
      0:   aload_0
      1:   getfield        #2; //Field bar:Ljava/lang/String;
      4:   areturn

Tenga en cuenta el "areturn" que significa "devolver una referencia"

Ahora si hacemos lo siguiente:

public String getBar() { 
    return 1; 
}

Devolverá los siguientes códigos de bytes:

public String getBar();
  Code:
   0:   iconst_1
   1:   ireturn

Ahora podemos ver que el tipo en la definición no coincide con el tipo de retorno de ireturn, lo que significa retorno int.

Así que realmente se reduce a que si el método tiene una ruta de retorno, esa ruta debe coincidir con el tipo de retorno. Pero hay instancias en el código de bytes en las que no se genera ninguna ruta de retorno y, por lo tanto, no se rompe la regla.

Philip Devine
fuente