Método Java com tipo de retorno compila sem instrução de retorno

228

Questão 1:

Por que o código a seguir é compilado sem ter uma declaração de retorno?

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

Aviso: Se adicionar retorno depois de um tempo, recebo um Unreachable Code Error.

Questão 2:

Por outro lado, por que o código a seguir compila,

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

mesmo que o seguinte não.

public int a(int b) {
    while(b == b);
}
Willi Mentzel
fonte
2
Não é uma duplicata do stackoverflow.com/questions/16789832/… , graças à segunda metade da 2ª pergunta.
TJ Crowder

Respostas:

274

Questão 1:

Por que o código a seguir é compilado sem ter uma declaração de retorno?

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

Isso é coberto pelo JLS§8.4.7 :

Se um método for declarado com um tipo de retorno (§8.4.5), ocorrerá um erro em tempo de compilação se o corpo do método puder ser concluído normalmente (§14.1).

Em outras palavras, um método com um tipo de retorno deve retornar apenas usando uma instrução de retorno que forneça um retorno de valor; o método não tem permissão para "deixar o final do corpo". Veja §14.17 para as regras precisas sobre instruções de retorno em um corpo de método.

É possível que um método tenha um tipo de retorno e ainda não contenha instruções de retorno. Aqui está um exemplo:

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

Como o compilador sabe que o loop nunca terminará ( truesempre é verdade, é claro), sabe que a função não pode "retornar normalmente" (deixar o final do corpo) e, portanto, não há problema return.

Questão 2:

Por outro lado, por que o código a seguir compila,

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

mesmo que o seguinte não.

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

No 0 == 0caso, o compilador sabe que o loop nunca terminará (isso 0 == 0sempre será verdadeiro). Mas não sabe disso b == b.

Por que não?

O compilador entende expressões constantes (§15.28) . Citação §15.2 - Formas de expressão (porque estranhamente esta frase não está em §15.28) :

Algumas expressões têm um valor que pode ser determinado em tempo de compilação. Essas são expressões constantes (§15.28).

No seu b == bexemplo, como há uma variável envolvida, ela não é uma expressão constante e não é especificada para ser determinada no momento da compilação. Podemos ver que sempre será verdade neste caso (embora, se bfosse um double, como o QBrute apontou , poderíamos ser facilmente enganados Double.NaN, o que não== é o próprio ), mas o JLS especifica apenas que expressões constantes são determinadas em tempo de compilação , ele não permite que o compilador tente avaliar expressões não constantes. O bayou.io levantou um bom argumento: por que não: Se você começa a tentar determinar expressões que envolvem variáveis ​​no momento da compilação, por onde você pára? b == bé óbvio (er, para nãoNaNvalores), mas e quanto a + b == b + a? Ou (a + b) * 2 == a * 2 + b * 2? Desenhar a linha em constantes faz sentido.

Portanto, como não "determina" a expressão, o compilador não sabe que o loop nunca será encerrado; portanto, ele acha que o método pode retornar normalmente - o que não é permitido, porque é necessário usá-lo return. Por isso, reclama da falta de um return.

TJ Crowder
fonte
34

Pode ser interessante pensar em um tipo de retorno de método não como uma promessa de retornar um valor do tipo especificado, mas como uma promessa de não retornar um valor que não seja do tipo especificado. Portanto, se você nunca devolver nada, não estará quebrando a promessa e, portanto, qualquer um dos seguintes itens será legal:

  1. Looping para sempre:

    X foo() {
        for (;;);
    }
    
  2. Recorrendo para sempre:

    X foo() {
        return foo();
    }
    
  3. Lançando uma exceção:

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

(Acho a recursão divertida de se pensar: O compilador acredita que o método retornará um valor do tipo X(o que quer que seja), mas não é verdade, porque não há código presente que tenha alguma idéia de como criar ou adquirir um X.)

Boann
fonte
8

Observando o código de bytes, se o que está sendo retornado não corresponder à definição, você receberá um erro de compilação.

Exemplo:

for(;;) mostrará os bytecodes:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Observe a falta de qualquer bytecode de retorno

Isso nunca atinge um retorno e, portanto, não retorna o tipo errado.

Para comparação, um método como:

public String getBar() { 
    return bar; 
}

Retornará os seguintes bytecodes:

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

Observe o "retorno", que significa "retornar uma referência"

Agora, se fizermos o seguinte:

public String getBar() { 
    return 1; 
}

Retornará os seguintes bytecodes:

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

Agora podemos ver que o tipo na definição não corresponde ao tipo de retorno de retorno, o que significa return int.

Então, na verdade, o que se resume é que, se o método tiver um caminho de retorno, esse caminho deverá corresponder ao tipo de retorno. Mas há instâncias no código de bytes em que nenhum caminho de retorno é gerado e, portanto, nenhuma quebra da regra.

Philip Devine
fonte