web-dev-qa-db-fra.com

Pourquoi l'instruction String Switch ne prend-elle pas en charge la casse NULL?

Je me demande simplement pourquoi l'instruction Java 7 switch ne prend pas en charge un cas null et renvoie plutôt NullPointerException? Voir la ligne commentée ci-dessous (exemple tiré de l'article sur les tutoriels Java sur switch ):

{
    String month = null;
    switch (month) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        //case null:
        default: 
            monthNumber = 0;
            break;
    }

    return monthNumber;
}

Cela aurait évité une condition if pour la vérification de la nullité avant chaque utilisation de switch.

108
Prashant Bhate

Comme damryfbfnetsi souligne dans les commentaires, JLS §14.11 a la note suivante:

L'interdiction d'utiliser null comme étiquette de commutateur empêche toute personne d'écrire du code qui ne peut jamais être exécuté. Si l'expression switch est d'un type référence, c'est-à-dire String ou d'un type primitif encadré ou d'un type enum, une erreur d'exécution se produira si l'expression est évaluée à null au moment de l'exécution. Selon les concepteurs du langage de programmation Java, il s'agit d'un meilleur résultat que de sauter en silence l'instruction switch entière ou de choisir d'exécuter les instructions (le cas échéant) après l'étiquette default (le cas échéant).

(c'est moi qui souligne)

Bien que la dernière phrase évite la possibilité d'utiliser case null:, elle semble raisonnable et offre un aperçu des intentions des concepteurs de langage.

Si nous examinons plutôt les détails de la mise en œuvre, cet article de blog de Christian Hujer émet des hypothèses éclairantes sur la raison pour laquelle null n'est pas autorisé dans les commutateurs (bien qu'il soit centré sur le commutateur enum plutôt que sur le commutateur String):

Sous le capot, l'instruction switch sera généralement compilée en un code d'octet tablesswitch. Et l'argument "physique" à switch ainsi que ses cas sont ints. La valeur int à activer est déterminée en appelant la méthode Enum.ordinal(). Les ordinaux [...] commencent à zéro.

Cela signifie que mapper null à 0 ne serait pas une bonne idée. Un commutateur sur la première valeur enum serait indiscernable de null. Cela aurait peut-être été une bonne idée de commencer à compter les ordinaux pour des enums de 1. Cependant, cela n'a pas été défini ainsi et cette définition ne peut pas être modifiée.

Alors que String commutateurs sont implémentés différemment , le commutateur enum est arrivé en premier et a défini le précédent de la manière dont l'activation d'un type de référence doit se comporter lorsque la référence est null.

134
Paul Bellora

En général, null est difficile à manipuler; peut-être qu'une meilleure langue peut vivre sans null.

Votre problème pourrait être résolu par

    switch(month==null?"":month)
    {
        ...
        //case "":
        default: 
            monthNumber = 0;

    }
25
ZhongYu

Ce n'est pas beau, mais String.valueOf() vous permet d'utiliser une chaîne nulle dans un commutateur. S'il trouve null, il le convertit en "null", sinon il renvoie simplement la même chaîne que celle que vous avez transmise. Si vous ne gérez pas "null" explicitement, il passera à default. Le seul inconvénient est qu'il n'y a aucun moyen de faire la distinction entre String "null" et une variable réelle null

    String month = null;
    switch (String.valueOf(month)) {
        case "january":
            monthNumber = 1;
            break;
        case "february":
            monthNumber = 2;
            break;
        case "march":
            monthNumber = 3;
            break;
        case "null":
            monthNumber = -1;
            break;
        default: 
            monthNumber = 0;
            break;
    }
    return monthNumber;
23
krispy

Ceci est une tentative de répondre à la raison pour laquelle il jette NullPointerException

La sortie de la commande javap ci-dessous révèle que case est choisi en fonction du hashcode de la chaîne d'argument switch et renvoie donc NPE lorsque .hashCode() est appelé sur une chaîne nulle. 

6: invokevirtual #18                 // Method Java/lang/String.hashCode:()I
9: lookupswitch  { // 3
    -1826660246: 44
     -263893086: 56
      103666243: 68
        default: 95
   }

Cela signifie basé sur les réponses à Le hashCode de Java peut-il produire la même valeur pour différentes chaînes? bien que rare, il est toujours possible que deux cas soient mis en correspondance (deux chaînes avec le même code de hachage) Voir cet ex ci-dessous

    int monthNumber;
    String month = args[0];

    switch (month) {
    case "Ea":
        monthNumber = 1;
        break;
    case "FB":
        monthNumber = 2;
        break;
    // case null:
    default:
        monthNumber = 0;
        break;
    }
    System.out.println(monthNumber);

javap pour lequel

  10: lookupswitch  { // 1
              2236: 28
           default: 59
      }
  28: aload_3       
  29: ldc           #22                 // String Ea
  31: invokevirtual #24                 // Method Java/lang/String.equals:(Ljava/lang/Object;)Z
  34: ifne          49
  37: aload_3       
  38: ldc           #28                 // String FB
  40: invokevirtual #24                 // Method Java/lang/String.equals:(Ljava/lang/Object;)Z
  43: ifne          54
  46: goto          59 //Default

ainsi que vous pouvez voir qu’un seul cas est généré, mais avec deux si condition pour vérifier mach avec chaque chaîne de cas. Moyen très intéressant et compliqué d’implémenter cette fonctionnalité!

14
Prashant Bhate

Longue histoire courte ... (et j'espère assez intéressant !!!)

Enum ont été introduits pour la première fois dans Java1.5 (Sep'2004) et le bug demandant à autoriser le basculement sur String a été archivé depuis longtemps (Oct '95). Si vous regardez le commentaire posté sur ce bogue à Jun'2004, il indique Don't hold your breath. Nothing resembling this is in our plans. On dirait qu'ils ont différé (ignoré) ce bogue et ont finalement lancé Java 1.5 la même année où ils ont introduit 'enum 'avec l'ordinal commençant à 0 et ayant décidé (raté) de ne pas prendre en charge null pour enum. Plus tard, dans Java1.7 (Jul'2011), ils ont suivi (forcé) la même philosophie que String (c.-à-d. Lors de la génération du bytecode, aucune vérification nulle n'a été effectuée avant d'appeler hashcode () méthode).

Donc, je pense que cela revient au fait que l'énumération est arrivée en premier et a été implémentée avec son ordinal commençant à 0, raison pour laquelle ils ne pouvaient pas supporter la valeur null dans le bloc switch, et plus tard avec String, ils ont décidé d'imposer la même philosophie autorisé dans le bloc de commutation.

TL; DR Avec String, ils pourraient s’occuper de NPE (suite à une tentative de génération de hashcode pour null) lors de l’implémentation du code Java pour la conversion du code octet, mais ont finalement décidé de ne pas le faire.

Réf: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO

4
sactiw

Selon Java Docs:

Un commutateur fonctionne avec les données primitives octet, short, char et int les types. Cela fonctionne aussi avec les types énumérés (discutés dans Types Enum), la classe String et quelques classes spéciales qui enveloppent certaines types primitifs: Character, Byte, Short et Integer (voir dans Nombres et Chaînes).

Puisque null n'a pas de type et n'est pas une instance de rien, cela ne fonctionnera pas avec une instruction switch.

1
BlackHatSamurai

La réponse est simplement que si vous utilisez un commutateur avec un type de référence (tel qu'un type primitif en boîte), l'erreur d'exécution se produira si l'expression est nulle, car unboxing enverrait le NPE.

donc case null (ce qui est illégal) ne pourra jamais être exécuté de toute façon;)

0
amrith

Je suis d'accord avec les commentaires perspicaces (Sous le capot ....) dans https://stackoverflow.com/a/18263594/1053496 dans la réponse de @Paul Bellora. 

J'ai trouvé une raison de plus de mon expérience.

Si 'case' peut être null, cela signifie que switch (variable) est nul, tant que le développeur fournit un cas de correspondance 'null' correspondant, nous pourrons alors affirmer que tout va bien. Mais que se passera-t-il si le développeur ne fournit aucun cas "null" correspondant? Ensuite, nous devons le faire correspondre à un cas "par défaut" qui peut ne pas correspondre à ce que le développeur avait l'intention de gérer dans le cas par défaut. Par conséquent, la correspondance de "null" avec une valeur par défaut pourrait provoquer un "comportement surprenant" ... ". Par conséquent, le fait de lancer" NPE "obligera le développeur à gérer explicitement tous les cas. J'ai trouvé que jeter des NPE dans ce cas était très réfléchi.

0
nantitv

Utilisez la classe Apache StringUtils

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
0
bhagat