La méthode Java avec type de retour est compilée sans instruction de retour

Question 1:

Pourquoi le code suivant comstack-t-il sans avoir une déclaration de retour?

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

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

Question 2:

Par contre, pourquoi le code suivant comstack-t-il,

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

même si ce qui suit ne fonctionne pas.

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

Question 1:

Pourquoi le code suivant comstack-t-il sans avoir une 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 retourner qu’en utilisant une instruction de retour qui fournit un retour de valeur; la méthode n’est pas autorisée à “laisser tomber 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 sans contenir d’instructions de retour. Voici un exemple:

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

Comme 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” (déposer la fin de son corps), donc il n’y a pas de return .

Question 2:

Par contre, pourquoi le code suivant comstack-t-il,

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

même si ce qui suit ne fonctionne pas.

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

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

Pourquoi pas?

Le compilateur comprend les expressions constantes (§15.28) . Citant §15.2 – Formes d’expressions (parce que cette phrase n’est pas étrange au §15.28) :

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

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

Il peut être intéressant de penser qu’un type de retour de méthode n’est pas une promesse de renvoyer une valeur du type spécifié, mais une promesse de ne pas renvoyer une valeur qui n’est pas du type spécifié. Ainsi, si vous ne retournez jamais rien, vous ne violez pas la promesse, et les conditions suivantes sont légales:

  1. En boucle pour toujours:

     X foo() { for (;;); } 
  2. Recursing pour toujours:

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

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

(Je trouve que la récursivité est amusante: le compilateur pense que la méthode va renvoyer une valeur de type X (peu importe ce que c’est), mais ce n’est pas vrai, car il n’y a pas de code ou obtenir un X )

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 bytecode de retour

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

Pour comparaison, une méthode comme:

 public Ssortingng getBar() { return bar; } 

Renverra les bytecodes suivants:

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

Notez le “retour” qui signifie “renvoyer une référence”

Maintenant, si nous faisons ce qui suit:

 public Ssortingng getBar() { return 1; } 

Renverra les bytecodes suivants:

 public Ssortingng 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 de ireturn, ce qui signifie que retour int.

Donc, en réalité, si la méthode a un chemin de retour, ce chemin doit correspondre au type de retour. Mais il y a des instances dans le bytecode où aucun chemin de retour n’est généré, et donc pas de violation de la règle.