Tutorial :Java Enums and Switch Statements - the default case?



Question:

For people suggesting throwing an exception:
Throwing an exception doesn't give me a compile-time error, it gives me a runtime error. I know I can throw an exception, I'd rather die during compilation than during runtime.

First-off, I am using eclipse 3.4.

I have a data model that has a mode property that is an Enum.

enum Mode {on(...), off(...), standby(...); ...}  

I am currently writing a view of this model and I have the code

...  switch(model.getMode()) {  case on:     return getOnColor();  case off:     return getOffColor();  case standby:     return getStandbyColor();  }  ...  

I am getting an error "This method must return a result of type java.awt.Color" because I have no default case and no return xxx at the end of the function. I want a compilation error in the case where someone adds another type to the enum (e.g. shuttingdown) so I don't want to put a default case that throws an AssertionError, as this will compile with a modified Mode and not be seen as an error until runtime.

My question is this:
Why does EclipseBuilder (and javac) not recognize that this switch covers all possibilities (or does it cover them?) and stop warning me about needing a return type. Is there a way I can do what I want without adding methods to Mode?

Failing that, is there an option to warn/error on switch statements that don't cover all of the Enum's possible values?

Edit: Rob: It is a compile error. I just tried compiling it with javac and I get a "missing return statement" error targeting the last } of the method. Eclispe just places the error at the top of the method.


Solution:1

You could always use the Enum with Visitor pattern:

enum Mode {    on {        public <E> E accept( ModeVisitor<E> visitor ) {           return visitor.visitOn();        }    },    off {        public <E> E accept( ModeVisitor<E> visitor ) {           return visitor.visitOff();        }    },    standby {        public <E> E accept( ModeVisitor<E> visitor ) {           return visitor.visitStandby();        }    }      public abstract <E> E accept( ModeVisitor<E> visitor );      public interface ModeVisitor<E> {        E visitOn();        E visitOff();        E visitStandby();    }  }  

Then you would implement something like the following:

public final class ModeColorVisitor implements ModeVisitor<Color> {      public Color visitOn() {         return getOnColor();      }        public Color visitOff() {         return getOffColor();      }        public Color visitStandby() {         return getStandbyColor();      }    }  

You'd use it as follows:

return model.getMode().accept( new ModeColorVisitor() );  

This is a lot more verbose but you'd immediately get a compile error if a new enum was declared.


Solution:2

You have to enable in Eclipse (window -> preferences) settings "Enum type constant not covered in switch" with Error level.

Throw an exception at the end of the method, but don't use default case.

public String method(Foo foo)    switch(foo) {    case x: return "x";    case y: return "y";    }      throw new IllegalArgumentException();  }  

Now if someone adds new case later, Eclipse will make him know he's missing a case. So don't ever use default unless you have really good reasons to do so.


Solution:3

I don't know why you get this error, but here is a suggestion, Why don't you define the color in the enum itself? Then you can't accidentally forget to define a new color.

For example:

import java.awt.Color;    public class Test {        enum Mode       {          on (Color.BLACK),           off (Color.RED),          standby (Color.GREEN);            private final Color color;           Mode (Color aColor) { color = aColor; }          Color getColor() { return color; }      }        class Model      {          private Mode mode;          public Mode getMode () { return mode; }      }        private Model model;        public Color getColor()      {          return model.getMode().getColor();      }     }  

btw, for comparison here is the original case, with compiler error.

import java.awt.Color;  public class Test {        enum Mode {on, off, standby;}        class Model      {          private Mode mode;          public Mode getMode () { return mode; }      }        private Model model;        public Color getColor()      {          switch(model.getMode()) {          case on:             return Color.BLACK;          case off:             return Color.RED;          case standby:             return Color.GREEN;          }      }     }  


Solution:4

I'd say it's probably because model.GetMode() could return null.


Solution:5

Create a default case that throws an exception:

throw new RuntimeExeption("this code should never be hit unless someone updated the enum")   

... and that pretty much describes why Eclipse is complaining: while your switch may cover all enum cases today, someone could add a case and not recompile tomorrow.


Solution:6

Why does EclipseBuilder not recognize that this switch covers all possibilities (or does it cover them?) and stop warning me about needing a return type. Is there a way I can do what I want without adding methods to Mode?

It's not an issue in Eclipse, but rather the compiler, javac. All javac sees is that you don't have a return value in the case in which nothing is matched (the fact that you know you are matching all cases is irrelevant). You have to return something in the default case (or throw an exception).

Personally, I'd just throw some sort of exception.


Solution:7

Your problem is that you are trying to use the switch statement as an indicator that your enum is locked down.

The fact is that the 'switch' statement and the java compiler cannot recognize that you do not want to allow other options in your enum. The fact that you only want three options in your enum is completely separate from your design of the switch statement, which as noted by others should ALWAYS have a default statement. (In your case it should throw an exception, because it is an unhandled scenario.)

You should liberally sprinkle your enum with comments so that everyone knows not to touch it, and you should fix your switch statement to throw errors for unrecognized cases. That way you've covered all the bases.

EDIT

On the matter of throwing compiler error. That does not strictly make sense. You have an enum with three options, and a switch with three options. You want it to throw a compiler error if someone adds a value to the enum. Except that enums can be of any size, so it doesn't make sense to throw a compiler error if someone changes it. Furthermore, you are defining the size of your enum based on a switch statement which could be located in a completely different class.

The internal workings of Enum and Switch are completely separate and should remain uncoupled.


Solution:8

A nice way for this would be to add the default case to return some error value or throw exception and to use automated tests with jUnit, for example:

@Test  public void testEnum() {    for(Mode m : Mode.values() {      m.foobar(); // The switch is separated to a method      // If you want to check the return value, do it (or if there's an exception in the       // default part, that's enough)    }  }  

When you got automated tests, this will take care of that foobar is defined for all enumerations.


Solution:9

Since I can't just comment...

  1. Always, Always, Always have a default case. You'd be surprised how "frequently" it would be hit (Less in Java than C, but still).

  2. Having said that, what if I only want to handle only on/off in my case. Your semantic processing by javac would flag that as an issue.


Solution:10

Nowadays (this answer is written several years after the original question), eclipse allows following configuration at Window -> Preferences -> Java -> Compiler -> Error/warnings -> Potential programming problems:

Incomplete switch cases

Signal even if default case exists


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »