56 votes

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

Normalement, la valeur par défaut n'est pas nécessaire dans une instruction de commutation. Cependant, dans la situation suivante, le code ne se compile avec succès que si je décommente l'instruction default. 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";
    }
}

58voto

templatetypedef Points 129554

La raison pour laquelle vous devez décommenter l'option default est que votre fonction dit qu'elle retourne un String mais si vous avez seulement case étiquettes définies pour A y B alors la fonction ne retournera pas de valeur si vous passez quelque chose d'autre. Java exige que toutes les fonctions qui déclarent retourner une valeur retournent effectivement 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 ont une valeur retournée.

Je crois (et je n'en suis pas sûr) que la raison en est que même si vous couvrez tous vos enum le code pourrait encore échouer dans certains cas. En particulier, supposons que vous compiliez le code Java contenant cette instruction switch (qui fonctionne parfaitement), puis que vous modifiiez ultérieurement l'instruction enum de sorte qu'il y a maintenant une troisième constante, disons C - mais vous ne recompilez pas le code avec l'option switch dans celui-ci. Maintenant, si vous essayez d'écrire du code Java qui utilise la classe précédemment compilée et qui passe en C dans cette déclaration, le code n'aura pas de valeur à retourner, violant ainsi le contrat Java selon lequel toutes les fonctions retournent toujours des valeurs.

D'un point de vue plus technique, je pense que la vraie raison est que le vérificateur de bytecode de la JVM rejette toujours les fonctions dans lesquelles il y a un chemin de contrôle qui tombe à la fin d'une fonction (voir §4.9.2 de la spécification de la 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 pour signaler qu'un problème existe.

51voto

Stephen C Points 255558

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

"V est [non] affecté après une déclaration de commutation si toutes les conditions suivantes sont remplies :

  • Soit il existe une étiquette par défaut dans le bloc de commutation, soit V est [non]attribué après l'expression de commutation.

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

OK ... J'extrapole les règles d'affectation définies 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 signifie pas qu'il n'y en a pas :-)


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

"L'ajout ou la réorganisation des constantes d'un type d'énumération ne rompt pas la compatibilité avec les binaires préexistants."

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


Dans Java 12, ils ont introduit des améliorations à switch qui incluent les expressions switch. Cela pose le même problème avec les enums qui changent entre la compilation et l'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 switch correspondante. (Il n'est évidemment pas nécessaire que les expressions switch soient exhaustives).

Dans la pratique, cela signifie normalement qu'une clause par défaut est nécessaire ; toutefois, dans le cas d'une expression de commutation d'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 de l'enum a changé entre la compilation et l'exécution. Le fait de s'appuyer sur cette insertion implicite de clause par défaut permet d'obtenir un code plus robuste ; désormais, 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 comme de l'eau de roche est ce que ferait la clause implicite par défaut. Je pense qu'elle lèverait une exception non vérifiée. (À l'heure actuelle, le JLS pour Java 12 n'a pas été mis à jour pour décrire les nouvelles expressions switch).

13voto

Adrian Points 1738

En Java 12, vous pouvez utiliser la fonction de prévisualisation des expressions de commutation ( 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 cas par défaut tant que vous traitez toutes les valeurs de l'enum en switch.

Notez que pour utiliser une fonction de prévisualisation, vous devrez passer --enable-preview --source 12 options pour javac y java

8voto

Peter Lawrey Points 229686

Comme cela a été dit, vous devez retourner une valeur et le compilateur ne suppose pas que l'enum ne peut pas changer dans le futur. Par exemple, vous pouvez créer une autre version de l'enum 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 donne le même résultat que

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

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

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

1voto

Andreas_D Points 64111

Il y a un contrat que cette méthode a pour retourner une chaîne de caractères, à moins qu'elle ne lève une exception. Et chaque fois ne se limite pas aux cas où la valeur de la xyz est égal à XVZ.A o XYZ.B .

Voici un autre exemple, où il s'agit de obviuos que le code s'exécute correctement mais où nous avons une erreur de compilation pour la même raison :

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

Ce n'est pas vrai que vous doivent ajouter une déclaration par défaut, il est vrai, que vous devez retourner une valeur à tout moment. Donc, soit vous ajoutez une instruction par défaut, soit vous ajoutez une instruction de retour après le bloc switch.

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