149

I am just wondering why the Java 7 switch statement does not support a null case and instead throws NullPointerException? See the commented line below (example taken from the Java Tutorials article on 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;
}

This would have avoided an if condition for null check before every switch use.

Prashant Bhate
  • 10,907
  • 7
  • 47
  • 82
  • 13
    No conclusive answer to this, since we're not the people who made the language. All answers will be pure conjecture. – asteri Aug 15 '13 at 23:22
  • 2
    An attempt to switch on `null` will cause an exception. Perform an `if` check for `null`, then go into the `switch` statement. – gparyani Aug 15 '13 at 23:22
  • 1
    Re: conjecture: It has taken more than ten years for this feature to land. I am sure there is ample discussion to be found about the reasoning behind the implementation that was finally chosen. – Thilo Aug 15 '13 at 23:24
  • 1
    Speaking of which, this reminds me of an old bug in Eclipse that would execute the default case on `null` being passed into it, whereas the specification says that it should throw an NPE. – gparyani Aug 15 '13 at 23:25
  • 1
    If I am not mistaken cases for switch are based on int and to create them java uses hashcodes of strings. – Pshemo Aug 15 '13 at 23:27
  • 1
    The link in the OP handles `NULL` before the switch statement is ever reached, you might consider doing likewise. – BlackHatSamurai Aug 15 '13 at 23:28
  • @JeffGohlke May be This Q is actually intended to the people who made the language and we are just curious audience ;) – Prashant Bhate Aug 15 '13 at 23:29
  • @JeffGohlke: Just because you didn't make something doesn't mean you don't know why it works the way it does. – user541686 Aug 15 '13 at 23:32
  • 1
    @Mehrdad I completely agree. That's why *how* questions are answerable. *Why* questions are not. Maybe it works that way because the creators were discussing it, then the lead developer spilled some coffee on his pants and got irritated, ending the meeting early. There is no way to answer a *why* question unless you are the person who made the decision. – asteri Aug 15 '13 at 23:33
  • 31
    From the [JLS](http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.11): *In the judgment of the designers of the Java programming language, [throwing a `NullPointerException` if the expression evaluates to `null` at runtime] is a better outcome than silently skipping the entire switch statement or choosing to execute the statements (if any) after the default label (if any).* – gparyani Aug 15 '13 at 23:37
  • 3
    @gparyani: Make that an answer. That sounds very official and definitive. – Thilo Aug 15 '13 at 23:42
  • 7
    @JeffGohlke: *"There is no way to answer a why question unless you are the person who made the decision."*... well, gparyani's comment proves otherwise – user541686 Aug 15 '13 at 23:51
  • @Mehrdad No it doesn't. It just moves the question to 'why did they form that judgment?' And it doesn't address the actual question of why there isn't a case null: at all. – user207421 Aug 16 '13 at 01:33

9 Answers9

160

As damryfbfnetsi points out in the comments, JLS §14.11 has the following note:

The prohibition against using null as a switch label prevents one from writing code that can never be executed. If the switch expression is of a reference type, that is, String or a boxed primitive type or an enum type, then a run-time error will occur if the expression evaluates to null at run time. In the judgment of the designers of the Java programming language, this is a better outcome than silently skipping the entire switch statement or choosing to execute the statements (if any) after the default label (if any).

(emphasis mine)

While the last sentence skips over the possibility of using case null:, it seems reasonable and offers a view into the language designers' intentions.

If we rather look at implementation details, this blog post by Christian Hujer has some insightful speculation about why null isn't allowed in switches (although it centers on the enum switch rather than the String switch):

Under the hood, the switch statement will typically compile to a tablesswitch byte code. And the "physical" argument to switch as well as its cases are ints. The int value to switch on is determined by invoking the method Enum.ordinal(). The [...] ordinals start at zero.

That means, mapping null to 0 wouldn't be a good idea. A switch on the first enum value would be indistinguishible from null. Maybe it would've been a good idea to start counting the ordinals for enums at 1. However it hasn't been defined like that, and this definition can not be changed.

While String switches are implemented differently, the enum switch came first and set the precedent for how switching on a reference type should behave when the reference is null.

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • 3
    It would have been a big improvment to allow null handling as part of a `case null:` if it would have been implemented exclusivily for `String`. Currently all `String` checking requires a null-check anyhow if we want to do it correct, although mostly implicitly by putting the string constant first as in `"123test".equals(value)`. Now we are forced to write our switch statement as in `if (value != null) switch (value) {...` – YoYo Feb 04 '16 at 23:06
  • 1
    Re "mapping null to 0 wouldn't be a good idea": that's an understatement since the value of "".hashcode() is 0! It would mean that a null string and a zero length string would have to be treated identically in a switch statement, which is clearly not viable. – skomisa Dec 16 '17 at 02:41
  • 2
    For the case of enum, what was stopping them from mapping null to -1? – krispy Sep 18 '19 at 02:54
40

In general null is nasty to handle; maybe a better language can live without null.

Your problem might be solved by

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

    }
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
31

It isn't pretty, but String.valueOf() allows you to use a null String in a switch. If it finds null, it converts it to "null", otherwise it just returns the same String you passed it. If you don't handle "null" explicitly, then it will go to default. The only caveat is that there is no way of distinguishing between the String "null" and an actual null variable.

    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
  • 1,244
  • 14
  • 19
21

This is an attempt to answer why it throws NullPointerException

The output of the javap command below reveals that case is chosen based on the hashcode of the switch argument string and hence throws NPE when .hashCode() is invoked on null string.

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

This means, based on answers to Can Java's hashCode produce same value for different strings?, though rare, there is still a possibility of two cases being matched (two strings with same hash code) See this example below

    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 for which

  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

As you can see only one case gets generated for "Ea" and "FB" but with two if conditions to check for a match with each case string. Very interesting and complicated way of implementing this functionality!

krispy
  • 1,244
  • 14
  • 19
Prashant Bhate
  • 10,907
  • 7
  • 47
  • 82
  • 5
    That could have been implemented in another way, though. – Thilo Aug 16 '13 at 00:24
  • 3
    I'd say this is a design bug. – Deer Hunter Aug 16 '13 at 03:25
  • 6
    I wonder if this ties in with why some dubious aspects of the string hash function haven't been changed: in general code shouldn't rely upon `hashCode` returning the same values on different runs of a program, but since string hashes get baked into executables by the compiler, the string hash method ends up being part of the language spec. – supercat Aug 22 '13 at 05:14
8

Long story short ... (and hopefully interesting enough!!!)

Enum were first introduced in Java1.5 (Sep'2004) and the bug requesting to allow switch on String was filed long back (Oct'95). If you look at the comment posted on that bug at Jun'2004, it says Don't hold your breath. Nothing resembling this is in our plans. Looks like they deferred (ignored) this bug and eventually launched Java 1.5 in the same year in which they introduced 'enum' with ordinal starting at 0 and decided (missed) not to support null for enum. Later in Java1.7 (Jul'2011) they followed (forced) the same philosophy with String (i.e. while generating the bytecode no null check was performed before calling hashcode() method).

So I think it boils down to the fact enum came in first and was implemented with its ordinal begin at 0 due to which they couldn't support null value in switch block and later with String they decided to forced the same philosophy i.e. null value not allowed in switch block.

TL;DR With String they could have take care of NPE (caused by attempt to generate hashcode for null) while implementing java code to byte code conversion but finally decided not to.

Ref: TheBUG, JavaVersionHistory, JavaCodeToByteCode, SO

Community
  • 1
  • 1
sactiw
  • 21,935
  • 4
  • 41
  • 28
1

According to Java Docs:

A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Enum Types), the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer (discussed in Numbers and Strings).

Since null has no type, and is not an instance of anything, it will not work with a switch statement.

BlackHatSamurai
  • 23,275
  • 22
  • 95
  • 156
  • 5
    And yet `null` is a valid value for a `String`, `Character`, `Byte`, `Short`, or `Integer` reference. – asteri Aug 15 '13 at 23:36
  • @asteri 7 years later, I'd like to point out that `switch` statement doesn't compare instance ref values; it compares instance values. – Dejay Clayton Jan 12 '21 at 23:40
0

The answer is simply that if you use a switch with a reference type (such as a boxed primitive type), the run-time error will occur if the expression is null because unboxing it would throw the NPE.

so case null (which is illegal) could never be executed anyway ;)

amrith
  • 953
  • 6
  • 17
  • 1
    That could have been implemented in another way, though. – Thilo Aug 15 '13 at 23:40
  • OK @Thilo, smarter people than me were involved in this implementation. If you know of other ways in which this could have been implemented, I would love to know what those are [and I'm sure there are others] so do share ... – amrith Aug 16 '13 at 02:02
  • 3
    Strings are not boxed primitive types, and the NPE does not occur because someone is trying to "unbox" them. – Thilo Aug 16 '13 at 02:15
  • @thilo, the other ways to implement this are, what? – amrith Aug 16 '13 at 10:30
  • 3
    `if (x == null) { // the case: null part } ` – Thilo Aug 19 '13 at 12:56
  • @Thilo: What if the code for `null` and the code for a particular non-null value should do the same thing? Is there any clean way to avoid repetition? – supercat Dec 28 '13 at 20:07
0

I agree with insightful comments (Under the hood ....) in https://stackoverflow.com/a/18263594/1053496 in @Paul Bellora's answer.

I found one more reason from my experience.

If 'case' can be null that means switch(variable) is null then as long as the developer provides a matching 'null' case then we can argue it's fine . But what will happen if the developer does not provide any matching 'null' case. Then we have to match it to a 'default' case which may not be what developer has intended to handle in the default case. Therefore matching 'null' to a default could cause 'surprising behaviour'. Therefore throwing 'NPE' will make the developer to handle every cases explicitly. I found throwing NPE in this case very thoughtful.

nantitv
  • 3,539
  • 4
  • 38
  • 61
0

Use Apache StringUtils class

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