La méthode Java avec le type de retour se compile sans instruction de retour

228

Question 1:

Pourquoi le code suivant se compile sans avoir de déclaration de retour?

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

Remarque: si j'ajoute un retour après un certain temps, j'obtiens un Unreachable Code Error.

Question 2:

D'un autre côté, pourquoi le code suivant se compile-t-il,

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

même si ce qui suit ne le fait pas.

public int a(int b) {
    while(b == b);
}
Willi Mentzel
la source
2
Pas un doublon de stackoverflow.com/questions/16789832/… , grâce à la seconde moitié de la 2e question.
TJ Crowder du

Réponses:

274

Question 1:

Pourquoi le code suivant se compile sans avoir de déclaration de retour?

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

Ceci est couvert par JLS§8.4.7 :

Si une méthode est déclarée avoir un type de retour (§8.4.5), alors une erreur de compilation se produit si le corps de la méthode peut se terminer normalement (§14.1).

En d'autres termes, une méthode avec un type de retour ne doit renvoyer qu'en utilisant une instruction return qui fournit un retour de valeur; la méthode n'est pas autorisée à «déposer l'extrémité de son corps». Voir §14.17 pour les règles précises sur les instructions de retour dans un corps de méthode.

Il est possible qu'une méthode ait un type de retour et ne contienne cependant aucune instruction de retour. Voici un exemple:

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

Puisque le compilateur sait que la boucle ne se terminera jamais ( truec'est toujours vrai, bien sûr), il sait que la fonction ne peut pas "retourner normalement" (déposer la fin de son corps), et donc il est normal qu'il n'y en ait pas return.

Question 2:

D'un autre côté, pourquoi le code suivant se compile-t-il,

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

même si ce qui suit ne le fait pas.

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

Dans ce 0 == 0cas, le compilateur sait que la boucle ne se terminera jamais (ce 0 == 0sera toujours vrai). Mais il ne le sait pas pour b == b.

Pourquoi pas?

Le compilateur comprend les expressions constantes (§15.28) . Citant le §15.2 - Formes d'expressions (car étrangement cette phrase n'est pas dans le §15.28) :

Certaines expressions ont une valeur qui peut être déterminée au moment de la compilation. Ce sont des expressions constantes (§15.28).

Dans votre b == bexemple, comme une variable est impliquée, ce n'est pas une expression constante et n'est pas spécifiée pour être déterminée au moment de la compilation. Nous pouvons voir que cela va toujours être vrai dans ce cas (bien que si bc'était un double, comme l'a souligné QBrute , nous pourrions facilement être dupés Double.NaN, ce qui n'est pas ==lui-même ), mais le JLS spécifie uniquement que les expressions constantes sont déterminées au moment de la compilation , il ne permet pas au compilateur d'essayer d'évaluer des expressions non constantes. bayou.io a soulevé un bon point pour pourquoi pas: si vous commencez à essayer de déterminer des expressions impliquant des variables au moment de la compilation, où vous arrêtez-vous? b == best évident (euh, pour lesNaNvaleurs), mais qu'en est-il a + b == b + a? Ou (a + b) * 2 == a * 2 + b * 2? Tracer la ligne aux constantes est logique.

Donc, comme il ne "détermine" pas l'expression, le compilateur ne sait pas que la boucle ne se terminera jamais, il pense donc que la méthode peut retourner normalement - ce qu'il n'est pas autorisé à faire, car il est nécessaire d'utiliser return. Il se plaint donc de l'absence d'un return.

TJ Crowder
la source
34

Il peut être intéressant de penser à un type de retour de méthode non pas comme une promesse de retourner une valeur du type spécifié, mais comme une promesse de ne pas retourner une valeur qui n'est pas du type spécifié. Ainsi, si vous ne retournez jamais rien, vous ne violez pas la promesse, et donc l'un des éléments suivants est légal:

  1. Boucler pour toujours:

    X foo() {
        for (;;);
    }
    
  2. Récursif pour toujours:

    X foo() {
        return foo();
    }
    
  3. Lever une exception:

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

(Je trouve la récursion amusante à penser: le compilateur pense que la méthode retournera une valeur de type X(quelle qu'elle soit), mais ce n'est pas vrai, car il n'y a pas de code présent qui ait une idée de la façon de créer ou procurer un X.)

Boann
la source
8

En regardant le code d'octet, si ce qui est retourné ne correspond pas à la définition, vous recevrez une erreur de compilation.

Exemple:

for(;;) affichera les bytecodes:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Notez l'absence de tout bytecode de retour

Cela ne frappe jamais un retour et ne retourne donc pas le mauvais type.

A titre de comparaison, une méthode comme:

public String getBar() { 
    return bar; 
}

Renvoie les bytecodes suivants:

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

Notez le "areturn" qui signifie "retourner une référence"

Maintenant, si nous faisons ce qui suit:

public String getBar() { 
    return 1; 
}

Renvoie les bytecodes suivants:

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

Nous pouvons maintenant voir que le type dans la définition ne correspond pas au type de retour d'ireturn, ce qui signifie return int.

Donc, vraiment, cela revient à dire que si la méthode a un chemin de retour, ce chemin doit correspondre au type de retour. Mais il y a des cas dans le bytecode où aucun chemin de retour n'est généré du tout, et donc aucune violation de la règle.

Philip Devine
la source