Why doesn't String switch statement support a null case?
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.
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 theswitch
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 tonull
at run time. In the judgment of the designers of the Java programming language, this is a better outcome than silently skipping the entireswitch
statement or choosing to execute the statements (if any) after thedefault
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 toswitch
as well as its cases areint
s. The int value to switch on is determined by invoking the methodEnum.ordinal()
. The [...] ordinals start at zero.That means, mapping
null
to0
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
.
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