Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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;

    }

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;

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!

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

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.

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 ;)

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.

Use Apache StringUtils class

String month = null;
switch (StringUtils.trimToEmpty(month)) {
    case "xyz":
        monthNumber=1;  
    break;
    default:
       monthNumber=0;
    break;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top