Perché l'istruzione String switch non supporta un caso null?


125

Mi chiedo solo perché l' switchistruzione Java 7 non supporta un nullcaso e invece lancia NullPointerException? Vedere la riga commentata di seguito (esempio tratto dall'articolo Tutorial Java suswitch ):

{
    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;
}

Ciò avrebbe evitato una ifcondizione per il controllo nullo prima di ogni switchutilizzo.


12
Nessuna risposta conclusiva a questo, dal momento che non siamo le persone che hanno creato la lingua. Tutte le risposte saranno pura congettura.
asteri

2
Un tentativo di accensione nullcauserà un'eccezione. Esegui un ifcontrollo null, quindi vai all'estratto switchconto.
gparyani

28
Dal JLS : a giudizio dei progettisti del linguaggio di programmazione Java, [lanciare un NullPointerExceptionse l'espressione restituisce nullin runtime] è un risultato migliore rispetto al saltare silenziosamente l'intera istruzione switch o scegliere di eseguire le istruzioni (se presenti) dopo etichetta predefinita (se presente).
gparyani

3
@gparyani: rendila una risposta. Sembra molto ufficiale e definitivo.
Thilo

7
@ JeffGohlke: "Non c'è modo di rispondere a una domanda sul perché a meno che tu non sia la persona che ha preso la decisione." ... beh, il commento di gparyani prova il contrario
user541686

Risposte:


145

Come sottolinea damryfbfnetsi nei commenti, JLS §14.11 ha la seguente nota:

Il divieto di utilizzare nullcome etichetta switch impedisce di scrivere codice che non può mai essere eseguito. Se l' switchespressione è di un tipo di riferimento, Stringovvero un tipo primitivo boxed o un tipo enum, si verificherà un errore in fase di esecuzione se l'espressione restituisce nullin fase di esecuzione. A giudizio dei progettisti del linguaggio di programmazione Java, questo è un risultato migliore rispetto al saltare silenziosamente l'intera switchistruzione o scegliere di eseguire le istruzioni (se presenti) dopo l' defaultetichetta (se presenti).

(enfasi mia)

Mentre l'ultima frase ignora la possibilità di utilizzo case null:, sembra ragionevole e offre una visione delle intenzioni dei progettisti del linguaggio.

Se guardiamo piuttosto ai dettagli dell'implementazione, questo post sul blog di Christian Hujer contiene alcune speculazioni approfondite sul motivo per cui nullnon è consentito negli switch (sebbene sia incentrato enumsull'interruttore piuttosto che Stringsull'interruttore):

switchDietro le quinte, l' istruzione verrà generalmente compilata in un codice byte tablesswitch. E l'argomento "fisico" switchcosì come i suoi casi sono ints. Il valore int da attivare viene determinato invocando il metodo Enum.ordinal(). Gli ordinali [...] iniziano da zero.

Ciò significa che la mappatura nullsu 0non sarebbe una buona idea. Un'opzione sul primo valore enum sarebbe indistinguibile da null. Forse sarebbe stata una buona idea iniziare a contare gli ordinali per enumerazioni da 1. Tuttavia non è stato definito in questo modo e questa definizione non può essere modificata.

Sebbene le Stringopzioni siano implementate in modo diverso , è stata la enumprima e ha stabilito il precedente per come l'attivazione di un tipo di riferimento dovrebbe comportarsi quando il riferimento è null.


1
Sarebbe stato un grande miglioramento consentire la gestione degli null come parte di un case null:se fosse stato implementato esclusivamente per String. Attualmente tutti i Stringcontrolli richiedono comunque un controllo nullo se vogliamo farlo correttamente, sebbene per lo più implicitamente inserendo prima la costante di stringa come in "123test".equals(value). Ora siamo costretti a scrivere la nostra dichiarazione di commutazione come inif (value != null) switch (value) {...
YoYo

1
Ri "mappare null su 0 non sarebbe una buona idea": è un eufemismo poiché il valore di "" .hashcode () è 0! Significherebbe che una stringa nulla e una stringa di lunghezza zero dovrebbero essere trattate in modo identico in un'istruzione switch, che è chiaramente non praticabile.
skomisa

Nel caso di enum, cosa impediva loro di mappare null su -1?
krispy

31

In generale nullè sgradevole da gestire; forse una lingua migliore può farne a meno null.

Il tuo problema potrebbe essere risolto da

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

    }

Non è una buona idea se monthè una stringa vuota: questo la tratterà allo stesso modo di una stringa nulla.
gparyani

13
Per molti casi trattare null come una stringa vuota può essere perfettamente ragionevole
Eric Woodruff

23

Non è carino, ma String.valueOf()ti consente di utilizzare una stringa nulla in uno switch. Se trova null, lo converte in "null", altrimenti restituisce semplicemente la stessa stringa che hai passato. Se non gestisci in modo "null"esplicito, andrà a default. L'unico avvertimento è che non c'è modo di distinguere tra la stringa "null"e una nullvariabile effettiva .

    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;

2
Credo che fare qualcosa di simile in java sia un antipattern.
Łukasz Rzeszotarski

2
@ ŁukaszRzeszotarski Questo è più o meno quello che volevo dire con "non è carino"
krispy

15

Questo è un tentativo di rispondere al motivo per cui lancia NullPointerException

L'output del comando javap di seguito rivela che caseviene scelto in base al codice hash della switchstringa dell'argomento e quindi genera NPE quando .hashCode()viene richiamato su una stringa nulla.

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

Ciò significa che, in base alle risposte a hashCode di Java, può produrre lo stesso valore per stringhe diverse? , sebbene raro, c'è ancora la possibilità che due casi vengano abbinati (due stringhe con lo stesso codice hash) Vedi questo esempio sotto

    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 per cui

  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

Come puoi vedere, viene generato un solo caso per "Ea"e "FB"ma con due ifcondizioni per verificare una corrispondenza con ciascuna stringa del caso. Modo molto interessante e complicato per implementare questa funzionalità!


5
Tuttavia, avrebbe potuto essere implementato in un altro modo.
Thilo

2
Direi che questo è un bug di progettazione.
Deer Hunter

6
Mi chiedo se questo si ricolleghi al motivo per cui alcuni aspetti dubbi della funzione hash di stringa non sono stati modificati: in generale il codice non dovrebbe fare affidamento sulla hashCoderestituzione degli stessi valori su esecuzioni diverse di un programma, ma poiché gli hash di stringa vengono inseriti in eseguibili da il compilatore, il metodo hash delle stringhe finisce per far parte delle specifiche del linguaggio.
supercat

5

Per farla breve ... (e si spera abbastanza interessante !!!)

Enum è stato introdotto per la prima volta in Java1.5 ( settembre 2004 ) e il bug che richiedeva di consentire l' attivazione di String è stato archiviato molto tempo fa ( ottobre 95 ). Se guardi il commento pubblicato su quel bug a giugno 2004 , dice che Don't hold your breath. Nothing resembling this is in our plans.sembra che abbiano rinviato ( ignorato ) questo bug e alla fine hanno lanciato Java 1.5 nello stesso anno in cui hanno introdotto 'enum' con ordinale a partire da 0 e deciso ( mancato ) per non supportare null per enum. Più tardi in Java 1.7 ( luglio 2011 ) hanno seguito ( forzato) la stessa filosofia con String (cioè durante la generazione del bytecode non è stato eseguito alcun controllo nullo prima di chiamare il metodo hashcode ()).

Quindi penso che si riduca al fatto che enum è arrivato per primo ed è stato implementato con il suo ordinale che inizia da 0 a causa del quale non potevano supportare il valore nullo nel blocco degli interruttori e in seguito con String hanno deciso di forzare la stessa filosofia, ovvero il valore nullo no consentito nel blocco interruttori.

TL; DR Con String avrebbero potuto prendersi cura di NPE (causato dal tentativo di generare hashcode per null) durante l'implementazione del codice Java in conversione di byte code, ma alla fine hanno deciso di non farlo.

Rif: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO


1

Secondo Java Docs:

Uno switch funziona con i tipi di dati primitivi byte, short, char e int. Funziona anche con i tipi enumerati (discussi in Tipi enum), la classe String e alcune classi speciali che racchiudono determinati tipi primitivi: Character, Byte, Short e Integer (discussi in Numbers and Strings).

Poiché nullnon ha tipo e non è un'istanza di nulla, non funzionerà con un'istruzione switch.


4
E tuttavia nullè un valore valido per una String, Character, Byte, Short, o Integerdi riferimento.
asteri

0

La risposta è semplicemente che se si utilizza uno switch con un tipo di riferimento (come un tipo primitivo boxed), l'errore di run-time si verificherà se l'espressione è nulla perché l'unboxing genererebbe l'NPE.

quindi case null (che è illegale) non potrebbe mai essere eseguito comunque;)


1
Tuttavia, avrebbe potuto essere implementato in un altro modo.
Thilo

OK @Thilo, persone più intelligenti di me sono state coinvolte in questa implementazione. Se conosci altri modi in cui questo avrebbe potuto essere implementato, mi piacerebbe sapere quali sono [e sono sicuro che ce ne sono altri] quindi condividi ...
amrith

3
Le stringhe non sono tipi primitivi inscatolati e l'NPE non si verifica perché qualcuno sta cercando di "unbox".
Thilo

@thilo, gli altri modi per implementarlo sono, cosa?
amrith

3
if (x == null) { // the case: null part }
Thilo

0

Sono d'accordo con commenti penetranti (Under the hood ....) in https://stackoverflow.com/a/18263594/1053496 nella risposta di @Paul Bellora.

Ho trovato un motivo in più dalla mia esperienza.

Se 'case' può essere nullo significa che switch (variabile) è nullo, fintanto che lo sviluppatore fornisce un caso 'nullo' corrispondente, possiamo sostenere che va bene. Ma cosa succederà se lo sviluppatore non fornisce alcun caso "nullo" corrispondente. Quindi dobbiamo abbinarlo a un caso "predefinito" che potrebbe non essere ciò che lo sviluppatore intendeva gestire nel caso predefinito. Pertanto la corrispondenza di "null" a un valore predefinito potrebbe causare un "comportamento sorprendente". Pertanto, lanciare "NPE" farà sì che lo sviluppatore gestisca esplicitamente ogni caso. Ho trovato molto premuroso lanciare NPE in questo caso.


0

Usa la classe StringUtils di Apache

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.