web-dev-qa-db-fra.com

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

Question 1:

Pourquoi le code suivant est-il compilé sans une instruction return?

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

Remarque: si j'ajoute return après tout ce temps, je reçois un Unreachable Code Error.

Question 2:

Par ailleurs, pourquoi le code suivant 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);
}
225
Willi Mentzel

Question 1:

Pourquoi le code suivant est-il compilé sans une instruction return?

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

Ceci est couvert par JLS§8.4.7 :

Si une méthode est déclarée avec un type de retour (§8.4.5), une erreur de compilation survient 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 doit renvoyer uniquement à l'aide d'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 aucune instruction de retour. Voici un exemple:

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

Étant donné que le compilateur sait que la boucle ne se terminera jamais (true est toujours vrai, bien sûr), il sait que la fonction ne peut pas "retourner normalement" non return.

Question 2:

Par ailleurs, pourquoi le code suivant 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 le 0 == 0 _ cas, le compilateur sait que la boucle ne se terminera jamais (que 0 == 0 sera toujours vrai). Mais il ne sait pas que pour b == b.

Pourquoi pas?

Le compilateur comprend les expressions constantes (§15.28) . Citant §15.2 - Formes d’expressions (car curieusement cette phrase n’est pas au §15.28):

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

Dans votre b == b exemple, étant donné qu’une variable est impliquée, il ne s’agit pas d’une expression constante et elle n’est pas spécifiée pour être déterminée lors de la compilation. Nous pouvons voir que cela sera toujours vrai dans ce cas (bien que si b était un double, QBrute a souligné , nous pourrions facilement être trompés par Double.NaN, qui est et non pas == lui-même ), mais le JLS spécifie uniquement que les expressions constantes sont déterminées lors de la compilation, il ne permet pas au compilateur d'essayer d'évaluer des expressions non constantes. bayou.io a soulevé un bon point à propos de pourquoi pas: si vous commencez à essayer de déterminer les expressions impliquant des variables au moment de la compilation, où vous arrêtez-vous? b == b est évident (euh, pour les valeurs non -NaN), mais qu’en est-il de 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 renvoyer normalement - ce qui n'est pas autorisé, car il est nécessaire d'utiliser return. Donc, il se plaint de l’absence de return.

274
T.J. Crowder

Il peut être intéressant de penser à un type de retour de méthode non comme une promesse de retourner une valeur du type spécifié, mais comme une promesse not pour renvoyer une valeur qui est not du type spécifié. Ainsi, si vous ne retournez jamais rien, vous ne respectez pas la promesse, de sorte que les cas suivants sont légaux:

  1. En boucle pour toujours:

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

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

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

(Je trouve la récursion intéressante à penser: le compilateur pense que la méthode retournera une valeur de type X (quoi que ce soit), mais ce n'est pas vrai, car il n'y a pas de code présent aucune idée de la création ou de l'obtention d'un X.)

33
Boann

En regardant le code d'octet, si ce qui est renvoyé 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 produit jamais de retour et ne renvoie donc pas le mauvais type.

A titre de comparaison, une méthode comme:

public String getBar() { 
    return bar; 
}

Renverra les bytecodes suivants:

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

Notez le "retour" qui signifie "renvoyer une référence"

Maintenant, si nous faisons ce qui suit:

public String getBar() { 
    return 1; 
}

Renverra les bytecodes suivants:

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

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

En résumé, 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é, et donc aucune violation de la règle.

8
Philip Devine