El método Java con tipo de retorno se compila sin declaración de retorno

5 minutos de lectura

avatar de usuario
Willi Mentzel

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 retorno 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 el siguiente no.

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

  • No es un duplicado de stackoverflow.com/questions/16789832/…, gracias a la segunda mitad de la segunda pregunta.

    –TJ Crowder

    2 de julio de 2015 a las 8:59

avatar de usuario
TJ Crowder

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 devolución debe devolver solo mediante una declaración de devolución que proporcione una devolución de valor; al método no se le permite “dejar caer el final de su cuerpo”. Consulte §14.17 para conocer las reglas precisas sobre declaraciones de retorno en el cuerpo de un método.

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

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

Dado que el compilador sabe que el ciclo nunca terminará (true siempre es cierto, por supuesto), sabe que la función no puede “regresar normalmente” (abandonar el final 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 el siguiente no.

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

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

¿Por que no?

El compilador entiende 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. Estos son expresiones constantes (§15.28).

En tus b == b ejemplo, 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. Nosotros puede ver que siempre va a ser cierto en este caso (aunque si b eran un doublecomo señaló QBrute, fácilmente podríamos ser engañados por Double.NaNcual es no == sí mismo), pero JLS solo especifica que las expresiones constantes se determinan en tiempo de compilación, no permite que el compilador intente evaluar expresiones no constantes. bayou.io planteó un buen punto de por qué no: si comienza a intentar determinar expresiones que involucran variables en el momento de la compilación, ¿dónde se detiene? b == b es obvio (eh, para no-NaN valores), pero ¿qué pasa con a + b == b + a? O (a + b) * 2 == a * 2 + b * 2? Dibujar la línea en las constantes tiene sentido.

Entonces, 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 cual no está permitido porque es necesario usar return. Por eso se queja de la falta de un return.

avatar de usuario
Boann

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

  1. Bucle para siempre:

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

    X foo() {
        return foo();
    }
    
  3. Lanzar 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 ningún código presente que tenga alguna idea de cómo crear o adquirir un X.)

avatar de usuario
Felipe Devine

Mirando 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 cualquier código de bytes de retorno

Esto nunca genera 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 bytecodes:

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

Tenga en cuenta el “retorno” que significa “devolver una referencia”

Ahora si hacemos lo siguiente:

public String getBar() { 
    return 1; 
}

Devolverá los siguientes bytecodes:

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 devolución de ireturn, lo que significa return int.

De lo que realmente se trata es de que si el método tiene una ruta de retorno, esa ruta debe coincidir con el tipo de retorno. Pero hay casos en el código de bytes en los que no se genera ninguna ruta de retorno y, por lo tanto, no se rompe la regla.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad