web-dev-qa-db-fra.com

Pourquoi la valeur par défaut est-elle requise pour un commutateur sur une énumération?

Normalement, la valeur par défaut n'est pas nécessaire dans une instruction switch. Cependant, dans la situation suivante, le code ne se compile correctement que lorsque je décommente l'instruction par défaut. Quelqu'un peut-il expliquer pourquoi?

public enum XYZ {A,B};
public static String testSwitch(XYZ xyz)
{
    switch(xyz)
    {
    case A:
        return "A";
    case B:
    //default:
        return "B";
    }
}
39
apoorv020

La raison pour laquelle vous devez décommenter le default est que votre fonction indique qu'elle renvoie un String, mais si vous n'avez que des étiquettes case définies pour A et B alors la fonction ne retournera pas de valeur si vous passez autre chose. Java nécessite que toutes les fonctions qui indiquent qu'elles renvoient une valeur retournent réellement une valeur sur tous les chemins de contrôle possibles, et dans votre cas, le compilateur n'est pas convaincu que toutes les entrées possibles aient une valeur renvoyée.

Je crois (et je ne suis pas sûr de cela) que la raison en est que même si vous couvrez tous vos cas enum, le code pourrait toujours échouer dans certains cas. En particulier, supposons que vous compiliez le Java contenant cette instruction switch (qui fonctionne très bien), puis modifiez plus tard le enum pour qu'il y ait maintenant une troisième constante - disons dites C - mais vous ne recompilez pas le code avec l'instruction switch. Maintenant, si vous essayez d'écrire Java code qui utilise le code précédent- classe compilée et passe dans C dans cette instruction, alors le code n'aura pas de valeur à retourner, violant le contrat Java que toutes les fonctions retournent toujours des valeurs).

Plus techniquement parlant, je pense que la vraie raison est que le vérificateur de bytecode JVM rejette toujours les fonctions dans lesquelles il existe un chemin de contrôle qui tombe à la fin d'une fonction (voir §4.9.2 de la spécification JVM ), et donc si le code devait être compilé, il serait de toute façon rejeté par la JVM au moment de l'exécution. Le compilateur vous donne donc l'erreur de signaler qu'un problème existe.

50
templatetypedef

Je pense que cela s'explique par les règles d'affectation définies JLS pour les instructions switch ( JLS 16.2.9 ) qui stipulent ce qui suit:

"V est [un] attribué après une instruction switch si toutes les conditions suivantes sont vraies:

  • Soit il y a une étiquette par défaut dans le bloc commutateur, soit V est [un] attribué après l'expression du commutateur.

Si nous appliquons ensuite cela à la notionnelle V qui est la valeur de retour de la méthode, nous pouvons voir que s'il n'y a pas de branche default, la valeur serait théoriquement non affectée.

OK ... J'extrapole des règles d'affectation définitives pour couvrir les valeurs de retour, et peut-être qu'elles ne le font pas. Mais le fait que je n'ai pas pu trouver quelque chose de plus direct dans la spécification ne veut pas dire que ce n'est pas là :-)


Il y a une autre raison (plus solide) pour laquelle le compilateur doit donner une erreur. Il découle des règles de compatibilité binaire pour enum ( JLS 13.4.26 ) qui stipulent ce qui suit:

"L'ajout ou la réorganisation de constantes à partir d'un type enum ne rompra pas la compatibilité avec les binaires préexistants."

Alors, comment cela s'applique-t-il dans ce cas? Supposons bien que le compilateur était autorisé à déduire que l'exemple d'instruction switch de l'OP renvoie toujours quelque chose. Que se passe-t-il si le programmeur modifie maintenant le enum pour ajouter une constante supplémentaire? Selon les règles de compatibilité binaire JLS, nous n'avons pas rompu la compatibilité binaire. Pourtant, la méthode contenant l'instruction switch peut désormais (selon son argument) retourner une valeur non définie. Cela ne peut pas se produire, donc le commutateur doit être un erreur de compilation.


Dans Java 12, ils ont introduit des améliorations pour le commutateur qui incluent des expressions de commutateur. Cela rencontre le même problème avec les énumérations qui changent entre le temps de compilation et le temps d'exécution. Selon le JEP 354 =, ils résolvent ce problème comme suit:

Les cas d'une expression switch doivent être exhaustifs; pour toutes les valeurs possibles, il doit y avoir une étiquette de commutateur correspondante. (De toute évidence, les instructions de changement ne doivent pas être exhaustives.)

En pratique, cela signifie normalement qu'une clause par défaut est requise; cependant, dans le cas d'une expression de commutateur enum qui couvre toutes les constantes connues, une clause par défaut est insérée par le compilateur pour indiquer que la définition d'énumération a changé entre la compilation et l'exécution. S'appuyer sur cette insertion implicite de clause par défaut rend le code plus robuste; maintenant, lorsque le code est recompilé, le compilateur vérifie que tous les cas sont explicitement traités. Si le développeur avait inséré une clause par défaut explicite (comme c'est le cas aujourd'hui), une erreur possible aurait été cachée.

La seule chose qui n'est pas claire est ce que la clause implicite implicite ferait réellement. Je suppose que cela lèverait une exception non contrôlée. (Pour l'instant, le JLS pour Java 12 n'a pas été mis à jour pour décrire les nouvelles expressions de commutateur.)

43
Stephen C

Comme cela a été dit, vous devez renvoyer une valeur et le compilateur ne suppose pas que l'énumération ne pourra pas changer à l'avenir. Par exemple. vous pouvez créer une autre version de l'énumération et l'utiliser sans recompiler la méthode.

Remarque: il existe une troisième valeur pour xyz qui est nulle.

public static String testSwitch(XYZ xyz) {
    if(xyz == null) return "null";
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    return xyz.getName();
}

Cela a le même résultat que

public static String testSwitch(XYZ xyz) {
     return "" + xyz;
}

La seule façon d'éviter un retour est de lever une exception.

public static String testSwitch(XYZ xyz) {
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    throw new AssertionError("Unknown XYZ "+xyz);
}
7
Peter Lawrey

Dans Java 12, vous pouvez utiliser la fonction d'expression de commutateur d'aperçu ( JEP-325 ) comme suit:

public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}

et vous n'avez pas besoin de casse par défaut tant que vous gérez toutes les valeurs d'énumération dans le commutateur.

Notez que pour utiliser une fonction d'aperçu, vous devrez passer --enable-preview --source 12 options pour javac et Java

3
Adrian

Il existe un contrat que cette méthode a pour renvoyer une chaîne sauf si elle lève une exception. Et à chaque fois n'est pas limité aux cas où la valeur de xyz est égale à XVZ.A ou XYZ.B.

Voici un autre exemple, où c'est obviuos , que le code fonctionnera correctement mais où nous avons une erreur de temps de compilation pour la même raison:

public boolean getTrue() {
  if (1 == 1) return true;
}

Il n'est pas vrai que vous devez ajouter une instruction par défaut, il est vrai, que vous devez renvoyer une valeur à tout moment. Donc, ajoutez une instruction par défaut ou ajoutez une instruction de retour après le bloc de commutation.

1
Andreas_D
default: throw new AssertionError();
0
irreputable

Parce que le compilateur ne peut pas deviner qu'il n'y a que deux valeurs dans enum et vous oblige à renvoyer la valeur de la méthode. (Cependant, je ne sais pas pourquoi il ne peut pas deviner, peut-être qu'il a quelque chose avec de la réflexion).

0
Alex Nikolaenkov