56 votes

Une dernière variable peut-elle être réaffectée dans catch, même si l'affectation est la dernière opération de try?

Je suis assez convaincu qu'ici

final int i;
try { i = calculateIndex(); }
catch (Exception e) { i = 1; }

i ne peuvent avoir déjà été attribué si le contrôle atteint la catch-bloc. Cependant, le compilateur Java n'est pas d'accord et les revendications the final local variable i may already have been assigned.

Y a t il une subtilité qui m'échappe ici, ou est-ce juste une faiblesse du modèle utilisé par le Langage Java Spécification pour identifier les éventuelles réaffectations? Mon principal souci des choses comme Thread.stop(), ce qui peut entraîner une exception levée "hors de l'air mince, mais je ne vois pas comment il peut être levée après l'affectation, qui est apparemment la dernière action au sein de l'essayer à bloc.

L'expression ci-dessus, si c'est permis, ferait beaucoup de mes méthodes plus simples. Notez que ce cas d'utilisation a soutien de première classe dans les langues, comme la Scala, employer systématiquement le Peut-être monade:

final int i = calculateIndex().getOrElse(1);

Je pense que ce cas d'utilisation sert une très bonne motivation pour permettre qu'un cas spécial où l' i est certainement pas assignée à l'intérieur de la catch-bloc.

Mise à JOUR

Après réflexion, je suis même plus certain que ce n'est qu'une faiblesse de la JLS modèle: si je déclare l'axiome "dans l'exemple présenté, i est certainement non assigné lorsque le contrôle atteint la catch-bloc", il ne sera pas en conflit avec un autre axiome ou un théorème. Le compilateur ne permet pas une lecture de i avant il est affecté dans le fourre-bloc, de sorte que le fait qu' i a été attribuée ou non ne peut être observée.

35voto

djechlin Points 18051

JLS chasse:

C'est une erreur de compilation si une variable est affectée à moins qu'elle est certainement non affecté (§16) immédiatement avant la cession.

Dit-chapitre 16:

V est certainement non attribuées avant un bloc catch iff toutes les conditions suivantes s'appliquent:

V est certainement non attribuées après le bloc try.
V est certainement non affectés, avant chaque instruction return qui appartient le bloc try.
V est certainement non attribuées après e dans chaque énoncé de la forme jeter e qui appartient le bloc try.
V est certainement non attribués après chaque instruction assert qui se produit dans le bloc try.
V est certainement non affectés, avant chaque instruction break qui appartient le bloc try et dont la rupture cible contient (ou est) le rapport d'essai.
V est certainement non affectés, avant chaque instruction continue qui appartient le bloc try et dont les continuer cible contient l'instruction try.

Le gras est de moi. Après l' try bloc, il est difficile de savoir si i est affectée.

En outre, dans l'exemple

final int i;
try {
    i = foo();
    bar();
}
catch(Exception e) { // e might come from bar
    i = 1;
}

Le texte en gras est la seule condition de prévenir le réel erronée cession i=1 d'être illégal. Donc c'est suffisant pour prouver qu'une meilleure condition de "sans aucun doute, non affecté" est nécessaire pour permettre le code dans votre message original.

Si les spécifications ont été révisées afin de remplacer cette condition de

V est certainement non attribuées après le bloc try, si le bloc catch attrape un décoché exception.
V est certainement non attribuées avant la dernière instruction est susceptible de lancer une exception de type pris par le bloc catch, si le bloc catch attrape un décoché exception.

Alors je crois que votre code est légal. (Pour le meilleur de mes analyses ad-hoc.)

J'ai soumis un JSR pour cela, que je m'attends à être ignoré, mais j'étais curieux de voir comment ils sont traités. Techniquement, le numéro de télécopieur est un champ obligatoire, j'espère qu'il va pas faire trop beaucoup de dégâts, si je suis entré +1-000-000-000.

18voto

user1676075 Points 1586

Je pense que la JVM est, malheureusement, de les corriger. Alors qu'intuitivement correct en regardant le code, il n'a de sens que dans le contexte de la recherche à l'IL. J'ai créé une simple méthode run() qui la plupart du temps imite votre cas (simplifié commentaires ici):

0: aload_0
1: invokevirtual  #5; // calculateIndex
4: istore_1
5: goto  17
// here's the catch block
17: // is after the catch

Ainsi, alors que vous ne pouvez pas facilement écrire du code pour tester cela, parce qu'il ne compile pas, l'appel de la méthode, le magasin de la valeur, et la passer à la capture de trois opérations distinctes. Vous pourriez (toutefois peu probable que peut-être) une exception se produire (Thread.interrupt() semble être le meilleur exemple) entre l'étape 4 et l'étape 5. Cela aurait pour conséquence d'entrer dans le bloc catch après je a été défini.

Je ne suis pas sûr que vous pourriez intentionnellement de faire que cela se produise avec une tonne de fils et les interruptions (et le compilateur ne pas vous laisser écrire que le code de toute façon), mais il est donc théoriquement possible que je puisse être défini, et vous pouvez entrer dans le bloc de gestion des exceptions, même avec ce simple code.

7voto

Kevin DiTraglia Points 9303

Pas aussi propre (et je soupçonne ce que vous faites déjà). Mais cela ajoute seulement 1 ligne supplémentaire.

 final int i;
int temp;
try { temp = calculateIndex(); }
catch (IOException e) { temp = 1; }
i = temp;
 

4voto

Marko Topolnik Points 77257

Ce n'est qu'un résumé des arguments les plus forts en faveur de la thèse que les règles actuelles pour certains affectation ne peut pas être détendu sans rupture de la cohérence (A), suivie par mes contre-arguments (B):

  • Un: le bytecode niveau de l'écriture de la variable n'est pas la dernière instruction au sein de l'essayer-bloc: par exemple, la dernière instruction est généralement goto sauter par-dessus le code de gestion des exceptions;

  • B: mais si les règles de l'état qu' i est certainement pas assignée à l'intérieur de la catch-bloc, sa valeur ne peut pas être observée. Un non observables valeur est aussi bon que pas de valeur;

  • Un: même si le compilateur déclare i comme définitivement non affecté, un outil de débogage pouvait encore voir la valeur;

  • B: en fait, un outil de débogage peut toujours accéder à un non initialisé la variable locale, qui va sur un typique de mise en œuvre ont aucune valeur arbitraire. Il n'y a pas de différence essentielle entre une variable non initialisée et une variable dont l'initialisation terminée brusquement après l'écriture ayant eu lieu. Quel que soit le cas particulier envisagé ici, l'outil doit toujours utiliser des métadonnées supplémentaires, à savoir pour chaque variable locale de la gamme des instructions où cette variable est certainement affecté et permettent seulement de sa valeur à observer pendant l'exécution se trouve dans la gamme.

Conclusion Finale:

La spécification peut recevoir régulièrement plus fine des règles qui permettrait à mon posté exemple pour compiler.

3voto

meriton Points 30447

Il est exact que si l'affectation est la dernière opération dans le bloc try, nous savons qu'en entrant dans le bloc catch la variable n'aura pas été affecté. Cependant, la formalisation de la notion de "dernière opération" permettrait d'accroître considérablement la complexité de la spécification. Considérer:

try {
    foo = bar();
    if (foo) {
        i = 4;
    } else {
        i = 7;
    }
}

Serait que la fonction soit utile? Je ne le pense pas, car une variable finale doit être attribué exactement une fois, pas au plus une fois. Dans votre cas, la variable serait non affecté si un Error est levée. Vous ne pouvez pas les soins à ce sujet si la variable s'exécute hors de portée de toute façon, mais ce n'est pas toujours le cas (il pourrait y avoir un autre bloc catch capture de l' Error, dans le même ou un rapport d'essai). Par exemple, pensez à:

final int i;
try {
    try {
        i = foo();
    } catch (Exception e) {
        bar();
        i = 1;
    }
} catch (Throwable t) {
    i = 0;
}

C'est exact, mais ne serait pas si l'appel à la barre (de) s'est produite après l'affectation i (comme dans la clause finally), ou nous utilisons un try-with-resources déclaration avec une ressource dont la méthode lève une exception.

La comptabilité pour qui ne peut qu'ajouter de la complexité à la spécification.

Enfin, il est simple de contourner:

final int i = calculateIndex();

et

int calculateIndex() {
    try {
        // calculate it
        return calculatedIndex;
    } catch (Exception e) {
        return 0;
    }
}

qui rend évident que je lui est assigné.

Bref, je pense que l'ajout de cette fonctionnalité permettrait d'ajouter une complexité significative à la spec pour peu d'avantages.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X