Saturday, April 5, 2008

No Java Switch on Long

Section 14.11 ("The switch Statement") of the Third Edition of the Java Language Specification lists the types that the Java switch statement can support switching on. The Java switch statement supports switching on byte, Byte, char, Character, int, Integer, short, Short, or an Enum. When I was first learning Java, it was somewhat surprising to me that Java supported these integral types (except Enum which was not available at that time!), but did not support long or Long. Switching on non-integral types did not make sense, of course, due to the inability to precisely compare non-integral types. However, it seemed like long should be a valid type to switch on as shown in the next code listing.
package longswitch;

/**
 * This example demonstrates the switch statement.
 */
public class SwitchExample
{
   public static void main(String[] arguments)
   {
      final long value = System.currentTimeMillis() % 5;
      switch ( value )
      {
         case 1 : System.err.println("One");
                  break;
         case 2 : System.err.println("Two");
                  break;
         case 3 : System.err.println("Three");
                  break;
         case 4 : System.err.println("Four");
                  break;
         default : System.err.println("Default");
            break;
      }
   }
}
When one tries to compile the above code, an error ("possible loss of precision") like that shown in the next image is encountered. As the Java Language Specification pointed out, a compiler error did result from using one of the types (long) that was not listed in the specification. The code below makes the compiler error go away and, in this simple case, there is probably no need to worry about any rounding or truncation that might occur in the conversion of the long to an int.
package longswitch;

/**
 * This example demonstrates the switch statement.
 */
public class SwitchExample
{
   public static void main(String[] arguments)
   {
      final long value = System.currentTimeMillis() % 5;
      switch ( (new Long(value)).intValue() )
      {
         case 1 : System.err.println("One");
                  break;
         case 2 : System.err.println("Two");
                  break;
         case 3 : System.err.println("Three");
                  break;
         case 4 : System.err.println("Four");
                  break;
         default : System.err.println("Default");
            break;
      }
   }
}
In practice, we rarely "need" to switch on a long and the ability to switch on an int or Enum are sufficient. In fact, especially before Enum was available, I would have liked the ability to switch on String more than on long. Of course, as I have blogged about before, some people insist you should never use the switch statement. The Scala language provides a switch-like match that does not use or require break statements (which Scala does not support). I think that switch statements can be a red flag indicating possible design problems, but I also reject the argument that they are always bad or always the worst possible approach to a particular problem.

5 comments:

Simon Kissane said...

Actually,

switch ((new Long(value)).intValue()) {

is unnecessary.

Simpler approach is:

switch ((int)value) {

In fact, new Long(v).intValue() will allocate memory on the heap, every time it is run; whereas (int)value doesn't need to.

Keith said...

just my opinion, but here are some articles of best practice:

1. never, *never* truncate a long by casting it to an int.

2. never switch on a long - it doesn't make any sense from a practical standpoint. if your code has listed a case for every value available to a java integer you are in serious trouble ...

@DustinMarx said...

Keith,

I agree wholeheartedly with both of your "articles of best practice."

I also hope that no code base out there needs to switch on anything close to 4,294,967,296 (-Integer.MIN_VALUE + Integer.MAX_VALUE + 1) integer values.

Although I'm not against switch statements in general, I do prefer to use enums when possible with them and I prefer polymorphism when available over switching.

Dustin

Unknown said...

As to "making sense" for switching on a long, I disagree and need to do it in case where this makes sense.

We have some middleware that passes a message id (a long), then a byte array of data. I set up my receiving program to handle a few cases (I only need a couple of the messages, not everyone). Best practice would be to have a switch/case statement to handle the few messages I want, then a default that says "Unexpected message".

This can, of course, be handled by a series of stacked if statements, but the code is now a little more cluttered and not as easy to read.

Chuck M.

AndrĂ¡s Sik said...

As to the previous comment you could make a workaround which does not really do any casting. In terms of efficiency it will be "worse" though than the chaining of if statements, but this definitely won't be an issue in the most typical cases.

So whenever I have to do something like this, I create enum wrappers with contructors handling long arguments to wrap the long values I need (of course I add a representation of the default value with a suitable long value eg 0L), and then switch that enum type.
Now to achieve this, you basically have to implement a static method which checks the current possible enum universe for the desired value, and returns a matching enum constant.


Example code:

enum Wrapper {

UNSPECIFIED(0L),
WRAPPER1(1L),
WRAPPER2(2L);

private final long wrapperID;

private Wrapper(long wrapperID) {
this.wrapperID = wrapperID;
}

public long getWrapperID() {
return wrapperID;
}

public static Wrapper valueFor(long wrapperID) {
Wrapper wrapperValue = Wrapper.UNSPECIFIED;
for (Wrapper wrapper : Wrapper.values()) {
if (wrapper.getWrapperID() == wrapperID) {
wrapperValue = wrapper;
break;
}
}
return wrapperValue;
}

}

Now as I mentioned, this is slower in terms of effectiveness, but probably a bit more easy on the eyes.