Friday, 2 February 2007

Java language - Enum Switch

Today I want to look at the switch statement for enums and how they could be better validated.

enum switch

Lets consider a simple enum:

public enum Colour {
  RED, BLUE, GREEN
}

And how we might use it in a switch statement:

Colour col = ...
switch (col) {
  case RED:
    // do stuff with red
    break;
  case BLUE:
    // do stuff with blue
    break;
  case GREEN:
    // do stuff with green
    break;
}

But what happens if we add another colour, BLACK, to the enum?

public enum Colour {
  RED, BLUE, GREEN, BLACK
}

At this point our nice switch statement silently continues without doing anything. Which probably isn't what we want. So, we would need to search our entire application to find switch statements referencing Colour and update them.

So, could we do better? Well, what if we could mark the switch statement as 'must contain all the values of the enum':

Colour col = ...
@CompleteEnumSwitch
switch (col) {
  case RED:
    // do stuff with red
    break;
  case BLUE:
    // do stuff with blue
    break;
  case GREEN:
    // do stuff with green
    break;
}

Now, with the new annotation, this piece of code won't compile (as BLACK isn't handled). We would need to either add a case for BLACK, or a default clause.

Using an annotation here seems appropriate. It doesn't affect the bytecode, but does validate it. In other words, its very similar to @Override.

There is a downside to the annotation approach - its compile time only. If BLACK is added to the enum without recompiling the switch statement, then there will be no error either at compile time or runtime. But this seems a reasonable balance for the feature.

There's another problem though - an annotation can't be added at this point in the code at present. However, JSR 308 is working on this right now, so hopefully this restriction will disappear.

Moving beyond @CompleteEnumSwitch, I could also envisage an @NoFallThroughSwitch which would make falling through not compile in switch statements.

Any thoughts on this concept?

BTW, thanks to Ricky Clarkson for talking about complete switches which triggered me to write this.

15 comments:

  1. Herman van Hovell2 February 2007 14:01

    In Eclipse you can set a compiler flag: "Enum type constant not covered on 'switch'". This'll result in the same the kind of functionality. Your other idea is also available under the same settings.

    Hope this helps.

    ReplyDelete
  2. Stephen Colebourne2 February 2007 14:34

    @Herman, I know about that setting, but its global. This concept allows you to choose the setting per switch statement.

    ReplyDelete
  3. Well, this is an annotation, sounds like it's a reasonable way to enhance the language.

    ReplyDelete
  4. Men, U complex the simple thing...

    U can just add a default case to the switch...
    and throw a AssertException or something else in the default case.

    ReplyDelete
  5. There is an alternative to what you've suggested, Stephen:

    If you move the code related to each enum type into a method on the enum, then the need for the switch statement vanishes.

    Color col = ...
    col.doSomething();

    doSomething() would, of course, need to be implemented for each color.

    ReplyDelete
  6. I'd like to see something done at the Map level. I practically never use switch statements, but do make use of maps () as dispatch tables. So the "switch" is generated at runtime, possibly dynamically through a configuration, and is a little more OO.

    Similar coverage issues come into play as you've outlined. It would also be nice to map a range of values to a single instance of an interface. Or, more generally, map on an arbitrary boolean instead of just equals.

    Some sort of Map extension or other Collections API improvement, with a cook() method to JIT compile the cases for optimization and speed would be a welcome improvement. This would also help you with your runtime needs.

    ReplyDelete
  7. Adding such an annotation seems useful at a first glance. But maybe, using enums in switch statements should require to have a default statement to guarantee a valid state in case of adding a value to the enum (at least, the programmer has to take it into account).

    Other than that, there is a general need for being able to attach annotations to statements other than assignments: A problem I often encounter is the need for SuppressWarnings to mark unchecked casts or autounboxing (and autoboxing, as eclipse does not allow to select warning for boxing/unboxing seperately) situations, which I checked and know of being safe. Usually, I have to put the annotation to the method, which may hide other issues and requires additional comments to explain where and why the annotation was added. This would be much clearer having the annotation where it belongs.

    ReplyDelete
  8. If we talk about best programming practices, then the whole point is meaningless since you simply shouldn't be using a switch :-)

    But the idea of adding a compiling-time validating annotations is different from a best programming practice, such as the default case, since it's a compiler rather runtime test. So IMHO the annotation proposed by Stephen is worth while.

    ReplyDelete
  9. Stephen,

    IDEA actually has an inspection for incomplete switches on enums. I don't think Eclipse does. If this inspection were standard (across both IDEs) then I'm not sure what benefit you'd get from making a completeSwitch part of the language. Further, without IDE support most programmers would be unlikely to bother using the annotation,, or would forget sometimes.

    Fabrizio,

    Presumably you would replace the enum switch with a visitor implementation. These are very verbose, a little hard to read, and don't scale well. E.g., when a class can accept more than one type of visitor, it gets awkward to see which is which.

    In comparison to the intention, which is clearly expressed in a switch statement, visitor makes flow harder to understand.

    The only real fault of switch (on enums at least) is break. Arguably it was a design mistake to allow incomplete switches on enums in the first place - if more Java forms were expressions rather than statements then more thought would go into things like this. In other words, if switch returned something, we'd already have the completeness checking. It can't be added as a compile error, and I don't think new warnings should appear (by default at least) for existing code; that was one of the things that scared some people into staying with 1.4.

    ReplyDelete
  10. Correction, Herman said that Eclipse supports the inspection too.

    Stephen, when you say that Eclipse's setting is global - it's a per-project setting. I think that's granular enough. If not, then you probably want a way of telling Eclipse not to give a warning for a particular switch statement, the inverse of your annotation - @IncompleteSwitch (meaning "yes, I know it's incomplete, trust me.").

    Vivek,

    The problem with your approach is that it puts the responsibility in the wrong place. A Color shouldn't know how to save itself to disk or make itself appear on the screen, for example. That's what dynamic dispatch (the visitor pattern in Java) is for, assuming you need to do different things for different instances of Color, which in itself would be strange.

    Stefan,

    Making default cases doesn't help to achieve compile time completeness, because forgetting to add code for a case won't show up at compile time with a default case present. That takes you to dynamic checking, which is possibly a bad thing in a language with a less sophisticated error-handling system than, say, Common Lisp's.

    Also, I think @SuppressWarnings gets in the way of reading code; one programmer's idea of what should be suppressed is different to anothers, and even one programmer may change his mind. It's better to set it in another part of the code, e.g., instead of using the language construct 'switch', use a method call, 'completeSwitch'. I don't think the closures proposal goes far enough to make that convenient though.

    Suggested syntax for making annotations available on arbitrary statements - make them available on blocks:

    @SuppressWarnings("unchecked")
    {
    List l=new ArrayList[String]();
    }

    ReplyDelete
  11. @Ricky
    I know, defaults are runtime only. But checking for such pitfalls at compile time may as well stay an IDE feature. I just don't want to see third-party code breaking, which I am actually only using in my application.
    Regarding annotations-anywhere I don't see the point of cluttering, as a decent programmer would have to document that fact of what gets suppressed on the spot anyway. For local variable definition using annotation already is possible, btw.
    I'm not sure about the connection to closures, but for making methods like a completeSwitch readable, one would need another extension to Java (e.g. Multipiece Method Names, as shown on my blog). I just don't see, how closures will help on deciding upon completeness, though.

    ReplyDelete
  12. Stefan,

    I liked your multipiece method names post, indeed.

    Closures would help in deciding upon completeness simply by making it clear what kind of switch statement you want. Imagine we have a similar proposal to closures, but for macros, and that completeSwitch is such a macro.

    import static java.lang.Control.completeSwitch;

    completeSwitch(enumValue)
    {
    case ONE:
    return 1;
    etc.
    }

    Then it could be clear to an IDE whether you want a complete switch or not, and more readable (subjective) than:

    @CompleteSwitch
    switch (blah)
    { etc }

    It might be possible using your multipiece method names idea to make the above happen with closures, albeit with other syntax.

    ReplyDelete
  13. Well, thanks for the compliment on my post.

    I think macros are somewhat completely different to closures. As with the default statement, as far as I can see, closures only would give a runtime check on completeness, as the compiler would not know about what completeness in calling a closure means. One would need to add the information on enums as kind of distinctive cardinality to a VarArg-like argument construct or add meta-information to add explanations for the compiler.
    But maybe I am thinking too complicated :)

    A Macro, on the other hand, would be some kind of ruleset, which tells the compiler how to construct the desired Java code from a given macro usage. Of course, a ruleset could contain constraints on what to build and fail or warn at compile time. But I would wonder, if anyone wants to have macros being introduced to Java.

    ReplyDelete
  14. YES,WATHEVER THE INFORMATION I GOT FROM HERE IT IS SOMEWHAT EASY FOR ME TO UNDERSTAND

    ReplyDelete
  15. Ricky: "assuming you need to do different things for different instances of Color, which in itself would be strange".

    What about:

    GREEN:accelerate();
    AMBER:accelerateReallyFast();
    RED:accelerateReallyReallyFastAndScream();

    Notice I couldn't really follow these with break;

    Who says the old uns aren't the best ;)

    rwt @CompleteEnumSwitch and @NoFallThroughSwitch - I'm in favour of both suggestions - anything that makes switches less prone to error has to be good.

    ReplyDelete