Pourquoi l'instruction de commutation String ne prend-elle pas en charge une casse nulle?

125

Je me demande simplement pourquoi l' switchinstruction Java 7 ne prend pas en charge un nullcas et jette à la place NullPointerException? Voir la ligne commentée ci-dessous (exemple tiré de l'article Tutoriels Java surswitch ):

{
    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 ifcondition de vérification nulle avant chaque switchutilisation.

Prashant Bhate
la source
12
Pas de réponse concluante à cela, puisque nous ne sommes pas ceux qui ont fait la langue. Toutes les réponses seront de pure conjecture.
asteri
2
Une tentative d'allumage nullprovoquera une exception. Effectuez une ifvérification null, puis entrez dans la switchdéclaration.
gparyani
28
D'après le JLS : De l'avis des concepteurs du langage de programmation Java, [lancer un NullPointerExceptionsi l'expression évalue à nullau moment de l'exécution] est un meilleur résultat que de sauter silencieusement toute l'instruction switch ou de choisir d'exécuter les instructions (le cas échéant) après le étiquette par défaut (le cas échéant).
gparyani
3
@gparyani: Faites-en une réponse. Cela semble très officiel et définitif.
Thilo
7
@JeffGohlke: "Il n'y a aucun moyen de répondre à une question pourquoi à moins que vous ne soyez la personne qui a pris la décision." ... eh bien, le commentaire de gparyani prouve le contraire
user541686

Réponses:

145

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

L'interdiction d'utiliser nullcomme étiquette de commutateur empêche d'écrire du code qui ne peut jamais être exécuté. Si l' switchexpression est d'un type référence, c'est-à-dire d' Stringun type primitif encadré ou d'un type enum, une erreur d'exécution se produit si l'expression est évaluée nullau moment de l'exécution. De l'avis des concepteurs du langage de programmation Java, c'est un meilleur résultat que de sauter silencieusement l' switchinstruction entière ou de choisir d'exécuter les instructions (le cas échéant) après l' defaultétiquette (le cas échéant).

(c'est moi qui souligne)

Si la dernière phrase fait abstraction de la possibilité d'utilisation case null:, elle semble raisonnable et offre un aperçu des intentions des concepteurs de langage.

Si nous regardons plutôt les détails de la mise en œuvre, ce billet de blog de Christian Hujer contient des spéculations perspicaces sur les raisons pour lesquelles les nullcommutateurs ne sont pas autorisés (bien qu'il se concentre sur le enumcommutateur plutôt que sur le Stringcommutateur):

Sous le capot, l' switchinstruction sera généralement compilée en un code d'octet de commutateur de table. Et l'argument «physique» switchainsi que ses arguments sont intle par. La valeur int à activer est déterminée en appelant la méthode Enum.ordinal(). Les ordinaux [...] commencent à zéro.

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

Bien que les Stringcommutateurs soient implémentés différemment , le enumcommutateur est venu en premier et a établi le précédent pour la façon dont la commutation sur un type de référence devrait se comporter lorsque la référence est null.

Paul Bellora
la source
1
Cela aurait été une grande amélioration d'autoriser la gestion des null dans le cadre d'un case null:si elle avait été implémentée exclusivement pour String. Actuellement, toute Stringvérification nécessite de toute façon une vérification nulle si nous voulons le faire correctement, bien que la plupart du temps implicitement en mettant la constante de chaîne en premier comme dans "123test".equals(value). Maintenant, nous sommes obligés d'écrire notre déclaration de commutation comme dansif (value != null) switch (value) {...
YoYo
1
Ré "mapper null à 0 ne serait pas une bonne idée": c'est un euphémisme puisque la valeur de "" .hashcode () est 0! Cela signifierait qu'une chaîne nulle et une chaîne de longueur zéro devraient être traitées de la même manière dans une instruction switch, ce qui n'est clairement pas viable.
skomisa
Pour le cas d'énumération, qu'est-ce qui les empêchait de mapper null à -1?
krispy
31

En général, il nullest désagréable à manipuler; peut-être qu'une meilleure langue peut vivre sans null.

Votre problème peut être résolu par

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

    }
ZhongYu
la source
Ce n'est pas une bonne idée s'il months'agit d'une chaîne vide: cela la traitera de la même manière qu'une chaîne nulle.
gparyani
13
Dans de nombreux cas, traiter null comme une chaîne vide peut être parfaitement raisonnable
Eric Woodruff
23

Ce n'est pas joli, 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 vous l'avez passé. Si vous ne gérez pas "null"explicitement, alors il ira à default. La seule mise en garde est qu'il n'y a aucun moyen de faire la distinction entre la chaîne "null"et une nullvariable réelle .

    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;
krispy
la source
2
Je crois que faire quelque chose comme ça en java est un anti-modèle.
Łukasz Rzeszotarski
2
@ ŁukaszRzeszotarski C'est à peu près ce que je voulais dire par "ce n'est pas joli"
krispy
15

Ceci est une tentative de répondre pourquoi il jette NullPointerException

La sortie de la commande javap ci-dessous révèle que le casechoix est basé sur le hashcode de la switchchaîne d'argument et, par conséquent, lève NPE lorsqu'il .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 que, sur la base des réponses au 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 exemple 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

Comme vous pouvez le voir, un seul cas est généré pour "Ea"et "FB"mais avec deux ifconditions pour vérifier la correspondance avec chaque chaîne de cas. Manière très intéressante et compliquée d'implémenter cette fonctionnalité!

Prashant Bhate
la source
5
Cela aurait pu être mis en œuvre d'une autre manière, cependant.
Thilo
2
Je dirais que c'est un bug de conception.
Deer Hunter
6
Je me demande si cela correspond à la raison pour laquelle certains aspects douteux de la fonction de hachage de chaîne n'ont pas été modifiés: en général, le code ne devrait pas reposer sur le hashCoderenvoi des mêmes valeurs sur différentes exécutions d'un programme, mais puisque les hachages de chaîne sont intégrés dans des exécutables par le compilateur, la méthode de hachage de chaîne finit par faire partie de la spécification du langage.
supercat du
5

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

Les Enum ont été introduits pour la première fois dans Java1.5 ( Sep'2004 ) et le bogue demandant d' autoriser le basculement sur String a été déposé depuis longtemps ( Oct'95 ). Si vous regardez le commentaire publié sur ce bogue en juin 2004 , il Don't hold your breath. Nothing resembling this is in our plans.semble 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 un ordinal commençant à 0 et ont décidé ( manqué ) pour ne pas prendre en charge null pour enum. Plus tard dans Java1.7 ( juillet 2011 ), ils ont suivi ( forcé) la même philosophie avec String (c'est-à-dire lors de la génération du bytecode, aucune vérification de null n'a été effectuée avant d'appeler la méthode hashcode ()).

Donc, je pense que cela se résume au fait que l'énumération est arrivée en premier et a été implémentée avec son ordinal commençant à 0 en raison de laquelle ils ne pouvaient pas prendre en charge la valeur nulle dans le bloc de commutation et plus tard avec String, ils ont décidé de forcer la même philosophie, c'est-à-dire que la valeur nulle n'est pas autorisé dans le bloc de commutation.

TL; DR Avec String, ils auraient pu prendre soin de NPE (causé par une tentative de génération de hashcode pour null) tout en implémentant le code java en conversion de code d'octet, mais ont finalement décidé de ne pas le faire.

Réf: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO

sactiw
la source
1

Selon Java Docs:

Un commutateur fonctionne avec les types de données primitifs byte, short, char et int. Il fonctionne également avec les types énumérés (décrits dans Enum Types), la classe String et quelques classes spéciales qui encapsulent certains types primitifs: Character, Byte, Short et Integer (discuté dans Numbers and Strings).

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

NoirChapeauSamouraï
la source
4
Et pourtant , nullest une valeur valide pour un String, Character, Byte, Shortou Integerréférence.
asteri
0

La réponse est simplement que si vous utilisez un commutateur avec un type de référence (tel qu'un type primitif encadré), l'erreur d'exécution se produira si l'expression est nulle car le déballage lèverait le NPE.

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

Amrith
la source
1
Cela aurait pu être mis en œuvre d'une autre manière, cependant.
Thilo
OK @Thilo, des gens plus intelligents que moi ont participé à cette implémentation. Si vous connaissez d'autres moyens par lesquels cela aurait pu être mis en œuvre, j'aimerais savoir ce que sont [et je suis sûr qu'il y en a d'autres], alors partagez-les ...
amrith
3
Les chaînes ne sont pas des types primitifs encadrés et le NPE ne se produit pas parce que quelqu'un essaie de les "déballer".
Thilo
@thilo, les autres façons de l'implémenter sont, quoi?
amrith
3
if (x == null) { // the case: null part }
Thilo
0

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 autre raison de mon expérience.

Si 'case' peut être null, cela signifie que le commutateur (variable) est nul, tant que le développeur fournit un cas 'nul' correspondant, nous pouvons affirmer que tout va bien. Mais que se passera-t-il si le développeur ne fournit pas de cas «nul» correspondant. Ensuite, nous devons le faire correspondre à un cas «par défaut» qui peut ne pas être ce que le développeur a prévu de gérer dans le cas par défaut. Par conséquent, faire correspondre «null» à une valeur par défaut pourrait provoquer un «comportement surprenant». Par conséquent, lancer 'NPE' obligera le développeur à gérer explicitement tous les cas. J'ai trouvé que lancer du NPE dans ce cas était très réfléchi.

nantitv
la source
0

Utiliser la classe Apache StringUtils

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