Tuesday, March 22, 2011

JDK 7: Java Switching on Strings Is Here

I have previously blogged on how Groovy and ActionScript provide demonstrations of switching on Strings functionality that has not been available in Java. JDK 7 changes this for Java by introducing the ability to switch on Strings. Specifically, Project Coin delivers on the promise of an ability to switch on Strings.

The next two code listings show String comparisons performed with the traditional if-else using equals approach and the newly available switching on strings approach.

StringsWithIfElseDemo.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Simple class demonstrating if/else comparisons of Strings.
 */
public class StringsWithIfElseDemo
{
   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      final String name =   arguments.length > 0
                          ? arguments[0]
                          : "Dustin";

      if (name.equals("Dino"))
      {
         out.println("Flintstones?");
      }
      else if (name.equals("Neo"))
      {
         out.println("Matrix?");
      }
      else if (name.equals("Gandalf"))
      {
         out.println("Lord of the Rings?");
      }
      else if (name.equals("Dustin"))
      {
         out.println("Inspired by Actual Events");
      }
      else
      {
         out.println("The Good, the Bad, and the Ugly?");
      }
   }
}

StringsWithSwitchDemo.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Simple class demonstrating switch on Strings available with JDK 7.
 */
public class StringsWithSwitchDemo
{
   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      final String name =   arguments.length > 0
                          ? arguments[0]
                          : "Dustin";

      switch (name)
      {
         case "Dino" :
            out.println("Flintstones?");
            break;
         case "Neo" :
            out.println("Matrix?");
            break;
         case "Gandalf" :
            out.println("Lord of the Rings?");
            break;
         case "Dustin" :
            out.println("Inspired by Actual Events");
            break;
         default :
            out.println("The Good, the Bad, and the Ugly?");
      }
   }
}

In the "old" days (before JDK 7), the second class would not compile. When compilation of the code containing switching on Strings is performed, a compiler error is encountered that states "incompatible types" and shows that a "String" was "found" while an "int" was "required." This is shown in the next screen snapshot.


With JDK 7, the second class that switches on Strings does compile. In my case, I have compiled it using the "Developer Preview" (Milestone 12) version of JDK 7 (build 134). Project Coin enhancements were scheduled for Milestone 11.

Once both classes above are compiled, they can be executed as demonstrated in the next screen snapshot.


Both classes behave exactly the same whether the traditional if-then-else is used in conjunction with String.equals(Object) or the new switching on Strings is used.

The javap utility can be run against the class files generated for both versions of String comparison code shown above and the results do have some differences. The following screen snapshot demonstrates running this tool against those classes.


As the image above shows, the size of the output from javap is larger for the code with switch statements than the code for if-else statements. About the only significant conclusion that can be made based on that size difference alone is that the classes are not exactly the same (which was already evident from the size of the .class files). I'm no expert at interpreting the output of the javap -v command, but nothing sticks out to me that significantly differentiates one approach from another in terms of performance. It seems that the somewhat subjective measure of "readability" remains the main reason for choosing between if-else and switch on Strings.

The Java documentation for switch is already updated with JDK 7 details. For example, it now states early on that "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" (I added the emphasis). The documentation also states (again I have added the emphasis) that "an if-then-else statement can test expressions based on ranges of values or conditions, whereas a switch statement tests expressions based only on a single integer, enumerated value, or String object."

Perhaps the most significant new text in Java documentation on switch is the addition of an entire section of this document titled "Using Strings in switch Statements" that talks about using Strings with switches and provides a code example of this. This section also points out a few items that may be seem minor, but are worth noting. First, it points out that the String being switched on should be checked for null first to avoid a NullPointerException. In my example above, this was unnecessary because the ternary operator used to set the String variable being switched on ensured that either at least one command line argument was specified (which should not be null in normal use of 'main') or else initialized the String to a non-null value ("Dustin") if no command line arguments were specified. The second point worth keeping in mind is that the switch on Strings behaves as String.equals(Object) behaves rather than as String.equalsIgnoreCase(String) behaves. This is appropriate because Strings with different cases are not necessarily the same, but it also means that the only control the developer has over case sensitivity issues is to change the String to lowercase [String.toLowerCase()] or uppercase [String.toUpperCase()] before switching on it (and then ensure the Strings in the case are the same case). Checking the String first for null and then converting to the expected case are often necessary to ensure the desired behavior.


The Desire for Switching on Strings

There is no question that some fellow Java developers are excited about the ability to switch on Strings. Two recent Java.net polls demonstrated this interest with 25% of respondents to the question "Which Project Coin (JSR 334) Java language enhancement will be most useful?" voting for the option "Strings in switch statements" (and 1/3 of respondents chose the "All of the above" option which obviously includes switching on strings). This was a follow-on to the previous Java.net poll question "What's the most important Java 7 enhancement for the work you do?" in which over half of all respondents voted for Project Coin.


Conclusion

I would have been much more likely to use the ability to switch on Strings several years ago before J2SE 5 made enums available to us. That being stated, there are times when I run into existing code or other situations where this ability can still increase code readability in a less risky and less costly way than redesign would require.

7 comments:

Martijn Verburg said...

Great post on the Strings in switch statement, not many people go into the depth that you have, e.g. Looking at the javap output

Please get in touch with us over at www.java7developer.com (or @java7developer on twitter) we'd love to have a chat to you about Java 7 related topics!

Cheers,
Martijn (aka @java7developer)

ouertani said...

thanks for this post, but I think that pattern matching in scala language already do more then java 7 switch :)

@DustinMarx said...

Martijn,

Thanks for the feedback and for the kind words. I'll definitely check out http://www.java7developer.com/.

Coincidentally, I purchased the MEAP ebook for The Well-Grounded Java Developer in late February. I have not had a chance to look at the portions available so far, but I do really like the idea of the book and the topics listed in the table of contents. It looks to be an interesting read, especially for developers with solid Java development experience.

Dustin

@DustinMarx said...

ouertani,

Thanks for the comment. I have only dabbled in Scala (mostly reading about it), but I have read or heard several people stating the virtues of Scala's pattern matching. I also like Groovy's ability to switch on any object whose class definition defines isCase(Object), but I still welcome Java adding features that Groovy and Scala already have because I still find myself in many more situations where Java is the language used and available than the other two.

The truth of it is that I likely won't switch much on Strings in new Java code and greenfield development because nearly any situation which has a finite number of Strings probably works better as an enum anyway.

Thanks again for the feedback.

Dustin

Bamby said...

A good post, with also very good links and pointers to other info on the subject!

Just a small correction though: the statement "In my example above, [checking for a null string] was unnecessary because the ternary operator used to set the String variable being switched on removed the possibility of a null." is not correct.

What the ternary operator does is make sure you are accessing an existent index of the array (so if arguments.length was 0, then arguments[0] would throw an ArrayIndexOutOfBoundsException). This does NOT make any assertions on the *contents* of that array element. There is nothing that prevents arguments[0], and thus your switch variable, from being null.

Greetings,

Mike

@DustinMarx said...

Mike,

Thanks for taking the time to leave a comment. You are correct that the wording I used was not as clear as it could have been. It so happens that the check in my post is okay because it's a unique case: the command-line arguments contents implicitly provided via the main function should not be null unless someone explicitly calls that main method from another piece of Java code and passes a null argument (and then I'm okay with a NullPointerException being thrown if someone does such a thing).

I'll likely change that portion of text because I agree that it's misleading. I think I'll change it from "this was unnecessary because the ternary operator used to set the String variable being switched on removed the possibility of a null" to something like "this was unnecessary because the ternary operator used to set the String variable being switched on ensured that either at least one command line argument was specified (which should not be null in normal use of 'main') or else initialized the String to a non-null value ('Dustin') if no command line arguments were specified." This should be a little clearer than what I had.

Had this not been the "special case" of handling the implicitly provided command-line arguments, I definitely would have needed to add a check for null on each element of the "args" array that I accessed and switched on.

Thanks again for taking the time to respond and helping me improve the content of this post.

Dustin

@DustinMarx said...

In the post Project Coin Fixes in 7u2, Joe Darcy states that a bug was fixed in Java 7 Update 2 so that parentheses can be used in case expressions. Like Darcy, I had not realized that this was as popular of a syntax as it appears to be.

Dustin