Warum unterstützt die String switch-Anweisung keinen Nullfall?


125

Ich frage mich nur, warum die Java 7- switchAnweisung keinen nullFall unterstützt und stattdessen wirft NullPointerException. Siehe die kommentierte Zeile unten (Beispiel aus dem Artikel über Java-Tutorialsswitch ):

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

Dies hätte eine ifBedingung für eine Nullprüfung vor jeder switchVerwendung vermieden .


12
Keine schlüssige Antwort darauf, da wir nicht die Leute sind, die die Sprache gemacht haben. Alle Antworten sind reine Vermutungen.
Asteri

2
Ein Einschaltversuch nullführt zu einer Ausnahme. Führen Sie eine ifPrüfung durch nullund gehen Sie dann in die switchAnweisung.
Gparyani

28
Aus dem JLS : Nach NullPointerExceptionAnsicht der Entwickler der Java-Programmiersprache ist [das Auslösen eines if, wenn der Ausdruck zur nullLaufzeit ausgewertet wird] ein besseres Ergebnis, als die gesamte switch-Anweisung stillschweigend zu überspringen oder die Anweisungen (falls vorhanden) nach dem auszuführen Standardbezeichnung (falls vorhanden).
Gparyani

3
@ gparyani: Machen Sie das eine Antwort. Das klingt sehr offiziell und definitiv.
Thilo

7
@ JeffGohlke: "Es gibt keine Möglichkeit, eine Warum-Frage zu beantworten, es sei denn, Sie sind die Person, die die Entscheidung getroffen hat." ... nun, gparyanis Kommentar beweist etwas anderes
user541686

Antworten:


145

Wie damryfbfnetsi weist darauf hin , in den Kommentaren, JLS §14.11 hat die folgende Anmerkung:

Das Verbot der Verwendung nullals Switch-Label verhindert, dass Code geschrieben wird, der niemals ausgeführt werden kann. Wenn es sich bei dem switchAusdruck um einen Referenztyp handelt, Stringdh um einen primitiven Typ mit Box oder einen Aufzählungstyp, tritt ein Laufzeitfehler auf, wenn der Ausdruck zur nullLaufzeit ausgewertet wird. Nach switchAnsicht der Entwickler der Java-Programmiersprache ist dies ein besseres Ergebnis, als die gesamte Anweisung stillschweigend zu überspringen oder die Anweisungen (falls vorhanden) nach dem defaultLabel (falls vorhanden) auszuführen .

(Hervorhebung von mir)

Während der letzte Satz die Möglichkeit der Verwendung überspringt case null:, erscheint er vernünftig und bietet einen Einblick in die Absichten der Sprachdesigner.

Wenn wir uns eher die Implementierungsdetails ansehen, enthält dieser Blog-Beitrag von Christian Hujer einige aufschlussreiche Spekulationen darüber, warum nullSwitches nicht zulässig sind (obwohl er sich enumeher auf den Switch als auf den StringSwitch konzentriert):

Unter der Haube wird die switchAnweisung normalerweise zu einem Tablesswitch-Bytecode kompiliert. Und das "physikalische" Argument switchsowie seine Fälle sind ints. Der einzuschaltende int-Wert wird durch Aufrufen der Methode bestimmt Enum.ordinal(). Die [...] Ordnungszahlen beginnen bei Null.

Das heißt, Mapping nullauf 0wäre keine gute Idee. Ein Einschalten des ersten Enum-Werts wäre von Null nicht zu unterscheiden. Vielleicht wäre es eine gute Idee gewesen, die Ordnungszahlen für Aufzählungen bei 1 zu zählen. Es wurde jedoch nicht so definiert, und diese Definition kann nicht geändert werden.

Während StringSwitches unterschiedlich implementiert sind , stand der enumSwitch an erster Stelle und legte den Präzedenzfall für das Verhalten eines Referenztyps fest, wenn sich die Referenz befindet null.


1
Es wäre eine große Verbesserung gewesen, die Nullbehandlung als Teil einer zuzulassen, case null:wenn sie exklusiv für implementiert worden wäre String. Gegenwärtig Stringerfordert jede Überprüfung ohnehin eine Nullprüfung, wenn wir dies korrigieren möchten, obwohl dies meistens implizit geschieht, indem die Zeichenfolgenkonstante wie in zuerst gesetzt wird "123test".equals(value). Jetzt sind wir gezwungen, unsere switch-Anweisung wie inif (value != null) switch (value) {...
YoYo

1
Zu "Null auf 0 abbilden wäre keine gute Idee": Das ist eine Untertreibung, da der Wert von "" .hashcode () 0 ist! Dies würde bedeuten, dass eine Nullzeichenfolge und eine Zeichenfolge mit der Länge Null in einer switch-Anweisung identisch behandelt werden müssten, was eindeutig nicht realisierbar ist.
Skomisa

Was hinderte sie im Fall von enum daran, null auf -1 abzubilden?
Krispy

31

Im Allgemeinen nullist es böse zu handhaben; Vielleicht kann eine bessere Sprache ohne leben null.

Ihr Problem könnte durch gelöst werden

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

    }

Keine gute Idee, wenn monthes sich um eine leere Zeichenfolge handelt: Dadurch wird sie genauso behandelt wie eine Nullzeichenfolge.
Gparyani

13
In vielen Fällen kann es durchaus sinnvoll sein, null als leere Zeichenfolge zu behandeln
Eric Woodruff,

23

Es ist nicht schön, aber String.valueOf()Sie können einen Null-String in einem Switch verwenden. Wenn es findet null, konvertiert es es in "null", andernfalls gibt es nur den gleichen String zurück, den Sie übergeben haben. Wenn Sie nicht "null"explizit behandeln, wird es zu gehen default. Die einzige Einschränkung ist, dass es keine Möglichkeit gibt, zwischen dem String "null"und einer tatsächlichen nullVariablen zu unterscheiden.

    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
Ich glaube, so etwas in Java zu machen, ist ein Antimuster.
Łukasz Rzeszotarski

2
@ ŁukaszRzeszotarski Das ist ziemlich genau das, was ich mit "es ist nicht schön"
gemeint

15

Dies ist ein Versuch zu beantworten, warum es wirft NullPointerException

Die Ausgabe des folgenden javap-Befehls zeigt, dass caseer basierend auf dem Hashcode der switchArgumentzeichenfolge ausgewählt wurde und daher NPE auslöst, wenn er .hashCode()für eine Nullzeichenfolge aufgerufen wird.

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

Dies bedeutet, basierend auf den Antworten auf Kann Javas HashCode denselben Wert für verschiedene Zeichenfolgen erzeugen? Obwohl selten, besteht immer noch die Möglichkeit, dass zwei Fälle übereinstimmen (zwei Zeichenfolgen mit demselben Hashcode). Siehe dieses Beispiel unten

    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 für welche

  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

Wie Sie sehen können, wird nur ein Fall für "Ea"und "FB"mit zwei ifBedingungen generiert , um eine Übereinstimmung mit jeder Fallzeichenfolge zu überprüfen. Sehr interessante und komplizierte Art der Implementierung dieser Funktionalität!


5
Das hätte aber auch anders umgesetzt werden können.
Thilo

2
Ich würde sagen, das ist ein Designfehler.
Deer Hunter

6
Ich frage mich, ob dies damit zusammenhängt, warum einige zweifelhafte Aspekte der String-Hash-Funktion nicht geändert wurden: Im Allgemeinen sollte Code nicht darauf beruhen, dass bei hashCodeverschiedenen Programmläufen dieselben Werte zurückgegeben werden, sondern dass String-Hashes von in ausführbare Dateien gebacken werden Im Compiler ist die String-Hash-Methode Teil der Sprachspezifikation.
Supercat

5

Lange Rede, kurzer Sinn ... (und hoffentlich interessant genug !!!)

Enum wurde erstmals in Java1.5 ( September 2004 ) eingeführt, und der Fehler , der das Einschalten des Strings zuließ, wurde vor langer Zeit ( Oktober 1995 ) abgelegt . Wenn Sie sich den Kommentar ansehen, der zu diesem Fehler im Juni 2004 veröffentlicht wurde , heißt es, dass Don't hold your breath. Nothing resembling this is in our plans.sie diesen Fehler zurückgestellt ( ignoriert ) und schließlich Java 1.5 im selben Jahr gestartet haben, in dem sie 'enum' mit Ordnungszahl ab 0 eingeführt und entschieden haben ( verpasst ), um null für enum nicht zu unterstützen. Später in Java1.7 ( Jul'2011 ) folgten sie ( gezwungen) die gleiche Philosophie wie bei String (dh beim Generieren des Bytecodes wurde vor dem Aufruf der Methode hashcode () keine Nullprüfung durchgeführt).

Ich denke, es läuft darauf hinaus, dass die Aufzählung zuerst einging und mit dem ordinalen Start bei 0 implementiert wurde, aufgrund dessen sie den Nullwert im Schalterblock nicht unterstützen konnten, und später mit String beschlossen sie, dieselbe Philosophie zu erzwingen, dh den Nullwert nicht im Schalterblock erlaubt.

TL; DR Mit String hätten sie sich um NPE kümmern können (verursacht durch den Versuch, Hashcode für null zu generieren), während sie die Konvertierung von Java-Code in Byte-Code implementierten, sich aber schließlich dagegen entschieden.

Ref: TheBUG , JavaVersionHistory , JavaCodeToByteCode , SO


1

Laut Java Docs:

Ein Switch arbeitet mit den primitiven Datentypen Byte, Short, Char und Int. Es funktioniert auch mit Aufzählungstypen (siehe Aufzählungstypen), der String-Klasse und einigen speziellen Klassen, die bestimmte primitive Typen umschließen: Zeichen, Byte, Kurz und Ganzzahl (siehe Zahlen und Zeichenfolgen).

Da nulles keinen Typ hat und keine Instanz von irgendetwas ist, funktioniert es nicht mit einer switch-Anweisung.


4
Und doch nullist ein gültiger Wert für eine String, Character, Byte, Short, oder IntegerReferenz.
Asteri

0

Die Antwort lautet einfach: Wenn Sie einen Switch mit einem Referenztyp (z. B. einem primitiven Typ mit Box) verwenden, tritt der Laufzeitfehler auf, wenn der Ausdruck null ist, da durch das Entpacken die NPE ausgelöst wird.

Fall null (was illegal ist) könnte also sowieso nie ausgeführt werden;)


1
Das hätte aber auch anders umgesetzt werden können.
Thilo

OK @Thilo, klügere Leute als ich waren an dieser Implementierung beteiligt. Wenn Sie andere Möglichkeiten kennen, wie dies hätte umgesetzt werden können, würde ich gerne wissen, was das sind [und ich bin sicher, dass es noch andere gibt], also teilen Sie ...
amrith

3
Strings sind keine primitiven Boxed-Typen, und die NPE tritt nicht auf, weil jemand versucht, sie zu "entpacken".
Thilo

@thilo, die anderen Möglichkeiten, dies zu implementieren, sind, was?
Amrith

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

0

Ich stimme aufschlussreichen Kommentaren (unter der Haube ...) in https://stackoverflow.com/a/18263594/1053496 in der Antwort von @Paul Bellora zu.

Ich habe aus meiner Erfahrung einen weiteren Grund gefunden.

Wenn 'case' null sein kann, was bedeutet, dass switch (variable) null ist, können wir argumentieren, dass es in Ordnung ist, solange der Entwickler einen passenden 'null'-Fall bereitstellt. Was passiert jedoch, wenn der Entwickler keinen passenden Nullfall bereitstellt? Dann müssen wir es einem 'Standard'-Fall zuordnen, der möglicherweise nicht das ist, was der Entwickler im Standardfall beabsichtigt hat. Daher kann das Anpassen von 'null' an einen Standardwert 'überraschendes Verhalten' verursachen. Wenn Sie also 'NPE' auslösen, kann der Entwickler alle Fälle explizit behandeln. Ich fand das Werfen von NPE in diesem Fall sehr nachdenklich.


0

Verwenden Sie die Apache StringUtils-Klasse

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.