Regardez la boucle infinie while
suivante en Java. Il provoque une erreur de compilation pour l'instruction en dessous.
while(true) {
System.out.println("inside while");
}
System.out.println("while terminated"); //Unreachable statement - compiler-error.
La même boucle infinie while
suivante, fonctionne cependant très bien et ne génère aucune erreur dans laquelle je viens de remplacer la condition par une variable booléenne.
boolean b=true;
while(b) {
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
Dans le deuxième cas également, l'instruction après la boucle est évidemment inaccessible car la variable booléenne b
est vraie, mais le compilateur ne se plaint pas du tout. Pourquoi?
Edit: La version suivante de while
est coincée dans une boucle infinie comme évidente mais n'émet aucune erreur de compilation pour l'instruction en dessous même si la condition if
dans la boucle est toujours false
et par conséquent, la boucle ne peut jamais revenir et peut être déterminée par le compilateur au moment de la compilation lui-même.
while(true) {
if(false) {
break;
}
System.out.println("inside while");
}
System.out.println("while terminated"); //No error here.
while(true) {
if(false) { //if true then also
return; //Replacing return with break fixes the following error.
}
System.out.println("inside while");
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
while(true) {
if(true) {
System.out.println("inside if");
return;
}
System.out.println("inside while"); //No error here.
}
System.out.println("while terminated"); //Compiler-error - unreachable statement.
Edit: Même chose avec if
et while
.
if(false) {
System.out.println("inside if"); //No error here.
}
while(false) {
System.out.println("inside while");
// Compiler's complain - unreachable statement.
}
while(true) {
if(true) {
System.out.println("inside if");
break;
}
System.out.println("inside while"); //No error here.
}
La version suivante de while
est également coincée dans une boucle infinie.
while(true) {
try {
System.out.println("inside while");
return; //Replacing return with break makes no difference here.
} finally {
continue;
}
}
Cela est dû au fait que le bloc finally
est toujours exécuté même si l'instruction return
se trouve devant lui dans le bloc try
lui-même.
Le compilateur peut facilement et sans équivoque prouver que la première expression toujours entraîne une boucle infinie, mais ce n'est pas aussi facile pour la seconde. Dans votre exemple de jouet, c'est simple, mais que faire si:
Le compilateur ne vérifie clairement pas votre cas plus simple, car il renonce complètement à cette route. Pourquoi? Parce que c'est plus dur interdit par la spécification. Voir section 14.21 :
(Soit dit en passant, mon compilateur se plaint lorsque la variable est déclarée final
.)
Selon les spécifications , ce qui suit est dit à propos des instructions while.
Une instruction while peut se terminer normalement si au moins l'une des conditions suivantes est vraie:
- L'instruction while est accessible et l'expression de condition n'est pas une expression constante avec la valeur true.
- Il existe une instruction break accessible qui quitte l'instruction while. \
Ainsi, le compilateur ne dira que le code suivant une instruction while est inaccessible si la condition while est une constante avec une valeur vraie, ou s'il y a une instruction break dans l'intervalle while. Dans le second cas, comme la valeur de b n'est pas une constante, il ne considère pas que le code qui le suit est inaccessible. Il y a beaucoup plus d'informations derrière ce lien pour vous donner plus de détails sur ce qui est et ce qui n'est pas considéré comme inaccessible.
Parce que true est constant et b peut être modifié dans la boucle.
Parce que l'analyse de l'état des variables est difficile, le compilateur a quasiment abandonné et vous laisse faire ce que vous voulez. De plus, la spécification de langage Java Language a des règles claires sur comment le compilateur est autorisé à détecter le code inaccessible .
Il existe de nombreuses façons de tromper le compilateur - un autre exemple courant est
public void test()
{
return;
System.out.println("Hello");
}
ce qui ne fonctionnerait pas, car le compilateur se rendrait compte que la zone était inaccessible. Au lieu de cela, vous pourriez faire
public void test()
{
if (2 > 1) return;
System.out.println("Hello");
}
Cela fonctionnerait car le compilateur est incapable de réaliser que l'expression ne sera jamais fausse.
Ce dernier n'est pas inaccessible. Le booléen b a toujours la possibilité d'être changé en faux quelque part à l'intérieur de la boucle provoquant une condition de fin.
Je suppose que la variable "b" a la possibilité de changer sa valeur, donc le compilateur pense que System.out.println("while terminated");
peut être atteint.
Les compilateurs ne sont pas parfaits - ils ne devraient pas l'être
La responsabilité du compilateur est de confirmer la syntaxe - pas de confirmer l'exécution. Les compilateurs peuvent finalement détecter et éviter de nombreux problèmes d'exécution dans un langage fortement typé - mais ils ne peuvent pas détecter toutes ces erreurs.
La solution pratique est d'avoir des batteries de tests unitaires pour compléter vos vérifications de compilateurs OR utiliser des composants orientés objet pour implémenter une logique qui est connue pour être robuste, plutôt que de s'appuyer sur des variables primitives et des conditions d'arrêt.
Strong Typing et OO: augmentation de l'efficacité du compilateur
Certaines erreurs sont de nature syntaxique - et en Java, le typage fort rend beaucoup d'exceptions d'exécution capturables. Mais, en utilisant de meilleurs types, vous pouvez aider votre compilateur à appliquer une meilleure logique.
Si vous souhaitez que le compilateur applique la logique plus efficacement, en Java, la solution consiste à créer des objets robustes et requis qui peuvent appliquer une telle logique, et à utiliser ces objets pour créer votre application, plutôt que des primitives.
Un exemple classique de ceci est l'utilisation du modèle d'itérateur, combiné avec la boucle foreach de Java, cette construction est moins vulnérable au type de bogue que vous illustrez qu'une boucle while simpliste.
En fait, je ne pense pas que quiconque ait bien compris (du moins pas dans le sens du questionneur d'origine). L'OQ continue de mentionner:
Correct, mais non pertinent, car b N'EST PAS modifié dans la boucle
Mais cela n'a pas d'importance car la dernière ligne IS accessible. Si vous avez pris ce code, l'a compilé dans un fichier de classe et a remis le fichier de classe à quelqu'un d'autre (par exemple en tant que bibliothèque), ils pourrait lier la classe compilée avec du code qui modifie "b" par réflexion, quittant la boucle et provoquant l'exécution de la dernière ligne.
Cela est vrai de toute variable qui n'est pas une constante (ou une finale qui se compile en une constante à l'emplacement où elle est utilisée - provoquant parfois des erreurs bizarres si vous recompilez la classe avec la finale et non une classe qui la référence, la référence classe conservera toujours l'ancienne valeur sans aucune erreur)
J'ai utilisé la capacité de réflexion pour modifier les variables privées non finales d'une autre classe pour patcher une classe dans une bibliothèque achetée - en corrigeant un bogue afin que nous puissions continuer à développer en attendant les correctifs officiels du fournisseur.
Soit dit en passant, cela peut ne pas fonctionner de nos jours - bien que je l'ai déjà fait, il y a une chance qu'une si petite boucle soit mise en cache dans le cache du processeur et puisque la variable n'est pas marquée comme volatile, le code mis en cache peut ne jamais prendre la nouvelle valeur. Je n'ai jamais vu cela en action mais je crois que c'est théoriquement vrai.
Le compilateur n'est pas assez sophistiqué pour parcourir les valeurs que b
peut contenir (bien que vous ne l'assigniez qu'une seule fois). Le premier exemple est facile pour le compilateur de voir que ce sera une boucle infinie car la condition n'est pas variable.
Je suis surpris que votre compilateur ait refusé de compiler le premier cas. Cela me semble étrange.
Mais le deuxième cas n'est pas optimisé pour le premier car (a) un autre thread peut mettre à jour la valeur de b
(b) la fonction appelée peut modifier la valeur de b
comme effet secondaire .
C'est simplement parce que le compilateur ne fait pas trop de travail de baby-sitting, bien que ce soit possible.
L'exemple montré est simple et raisonnable pour que le compilateur détecte la boucle infinie. Mais que diriez-vous d'insérer 1000 lignes de code sans aucune relation avec la variable b
? Et que diriez-vous que ces déclarations sont toutes b = true;
? Le compilateur peut certainement évaluer le résultat et vous dire qu'il est finalement vrai dans la boucle while
, mais à quel point sera-t-il lent de compiler un vrai projet?
PS, l'outil à charpie devrait certainement le faire pour vous.
Du point de vue du compilateur, le b
dans while(b)
pourrait changer en faux quelque part. Le compilateur ne prend tout simplement pas la peine de vérifier.
Pour le plaisir, essayez while(1 < 2)
, for(int i = 0; i < 1; i--)
etc.
Si le compilateur peut déterminer de manière concluante que le booléen sera évalué à true
au moment de l'exécution, il lèvera cette erreur. Le compilateur suppose que la variable que vous avez déclarée peut être modifiée (bien que nous sachions ici que les humains ne le feront pas).
Pour souligner ce fait, si les variables sont déclarées comme final
en Java, la plupart des compilateurs lèveront la même erreur que si vous substituiez la valeur. En effet, la variable est définie au moment de la compilation (et ne peut pas être modifiée au moment de l'exécution) et le compilateur peut donc déterminer de manière concluante que l'expression est évaluée à true
au moment de l'exécution.
La première instruction résulte toujours en une boucle infinie car nous avons spécifié une constante en condition de boucle while, où comme dans le deuxième cas le compilateur suppose qu'il y a possibilité de changement de valeur de b dans la boucle.
Les expressions sont évaluées au moment de l'exécution, donc, lorsque vous remplacez la valeur scalaire "true" par quelque chose comme une variable booléenne, vous avez changé une valeur scalaire en une expression booléenne et donc, le compilateur n'a aucun moyen de la connaître au moment de la compilation.