What is the usage of default when the switch is for an enum?

JavaEnums

Java Problem Overview


Suppose I have an enum Color with 2 possible values: RED and BLUE:

public enum Color {
	RED,
	BLUE
}

Now suppose I have a switch statement for this enum where I have code for both possible values:

Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
   ...
   break;

case BLUE:
   ...
   break;

default:
   break;
}

Since I have code block for both possible values of the enum, what is the usage of default in the above code?

Should I throw an exception if the code somehow reaches the default block like this?

Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
   ...
   break;

case BLUE:
   ...
   break;

default:
   throw new IllegalArgumentException("This should not have happened");
}

Java Solutions


Solution 1 - Java

It is good practice to throw an Exception as you have shown in the second example. You improve the maintainability of your code by failing fast.

In this case it would mean if you later (perhaps years later) add an enum value and it reaches the switch statement you will immediately discover the error.

If the default value were not set, the code would perhaps run through even with the new enum value and could possibly have undesired behavior.

Solution 2 - Java

The other answers are correct in saying that you should implement a default branch that throws an exception, in case a new value gets added to your enum in the future. However, I would go one step further and question why you're even using a switch statement in the first place.

Unlike languages like C++ and C#, Java represents Enum values as actual objects, which means that you can leverage object-oriented programming. Let's say that the purpose of your method is to provide an RGB value for each color:

switch (color)
    case RED:
       return "#ff0000";
    ...

Well, arguably, if you want each color to have an RGB value, you should include that as part of its description:

public enum Color
{
    RED("#FF0000"),
    BLUE("#0000FF");

    String rgb;
    public Color(String rgb) {
        this.rgb = rgb;
    }
    public getRgb() { return this.rgb; }
}

That way, if you add a new color later, you're pretty much forced to provide an RGB value. It's even more fail-fast than the other approach, because you'll fail at compile-time rather than run-time.

Note that you can do even more complicated things if you need to, including having each color provide its own custom implementation of an abstract method. Enums in Java are really powerful and object-oriented, and in most cases I've found I can avoid needing to switch on them in the first place.

Solution 3 - Java

Compile time completeness of the switch cases doesn't guarantee runtime completenes.

Class with a switch statement compiled against an older version of enum may be executed with a newer enum version (with more values). That's a common case with library dependencies.

For reasons like these, the compiler considers the switch without default case incomplete.

Solution 4 - Java

In small programs, there is no practical use for that, but think of a complex system that speards among large number of files and developers - if you define the enum in one file and use it in another one, and later on someone adds a value to the enum without updating the switch statement, you'll find it very useful...

Solution 5 - Java

If you've covered all of the possibilities with your various cases and the default cannot happen, this is the classic use case for assertions:

Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
    case RED:
       // ...
       break;
    
    case BLUE:
       // ...
       break;
    
    default:
       assert false; // This cannot happen
       // or:
       throw new AssertionError("Invalid Colors enum");
}

Solution 6 - Java

To satisfy IDEs and other static linters, I often leave the default case in as a no-op, along with a comment such as // Can't happen or // Unreachable

i.e., if the switch is doing the typical thing of handling all possible enum values, either explicitly or via fall-throughs, then the default case is probably programmer error.

Depending on the application, I sometimes put an assertion in the case to guard against programmer error during development. But this has limited value in shipping code (unless you ship with assertions enabled.)

Again, depending on the situation I might be convinced to throw an Error, as this is really an unrecoverable situation -- nothing the user can do will correct what is probably programmer error.

Solution 7 - Java

Yes, you should do it. You may change enum but don't change switch. In the future it'll lead to mistakes. I think that throw new IllegalArgumentException(msg) is the good practice.

Solution 8 - Java

When the enum constants are too many and you need to handle only for few cases, then the default will handle the rest of the constants.

Also, enum constants are references, if the reference is not yet set, or null. You may have to handle such cases too.

Solution 9 - Java

Yes, it is dead code until someone add a value to the enum, which will make your switch statement follow the principle of 'fail fast' (https://en.wikipedia.org/wiki/Fail-fast)

This could relates to this question : https://stackoverflow.com/questions/16797529/how-to-ensure-completeness-in-an-enum-switch-at-compile-time

Solution 10 - Java

Apart from the possible future extending of the enum, which was pointed out by many, some day someone may 'improve' yout getColor() or override it in a derived class and let it return an invalid value. Of course a compiler should catch that, unless someone explicitly forces unsafe type casting...

But bad things just happen, and it's a good practice not to leave any unexpected else or default path unguarded.

Solution 11 - Java

I'm surprised nobody else mentioned this. You can cast an int to an enum and it won't throw just because the value is not one of the enumerated values. This means (among other things), the compiler cannot tell that all the enum values are in the switch.

Even if you write your code correctly, this really does come up when serializing objects that contain enums. A future version might add to the enum and your code choke on reading it back, or somebody looking to create mayhem may hexedit a new value in. Either way, running off the switch rarely does the right thing. So, we throw in default unless we know better.

Solution 12 - Java

Here is how I would handle it, beside NULL value which would result in a null pointer exception which you can handle.

If Color color is not null, it has to be one of the singletons in enum Color, if you assign any reference to an object that is not one of the them this will cause a Runtime error.

So my solution is to account for values that are not supported.

Test Run

Test.java

public Test
{
  public static void main (String [] args)
  {
    try { test_1(null); }
    catch (NullPointerException e) { System.out.println ("NullPointerException"); }

    try { test_2(null); }
    catch (Exception e) { System.out.println(e.getMessage()); }

    try { test_1(Color.Green); }
    catch (Exception e) { System.out.println(e.getMessage()); }
  }

  public static String test_1 (Color color) throws Exception
  {
    String out = "";
    
    switch (color) // NullPointerException expected
    {
      case Color.Red:
        out = Red.getName();
        break;
      case Color.Blue:
        out = Red.getName();
        break;
      default:
        throw new UnsupportedArgumentException ("unsupported color: " + color.getName());
    }

    return out;
  }

.. or you can consider null as unsupported too

  public static String test_2 (Color color) throws Exception
  {
    if (color == null) throw new UnsupportedArgumentException ("unsupported color: NULL");
    return test_1(color);
  }
}

Color.java

enum Color
{
  Red("Red"), Blue("Blue"), Green("Green");
  private final String name;
  private Color(String n) { name = n; }
  public String getName() { return name; }
}

UnsupportedArgumentException.java

class UnsupportedArgumentException extends Exception
{
  private String message = null;

  public UnsupportedArgumentException() { super(); }

  public UnsupportedArgumentException (String message)
  {
    super(message);
    this.message = message;
  }

  public UnsupportedArgumentException (Throwable cause) { super(cause); }

  @Override public String toString() { return message; }

  @Override public String getMessage() { return message; }
}

Solution 13 - Java

In this case using Assertion in default is the best practice.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChumboChappatiView Question on Stackoverflow
Solution 1 - JavaDaineschView Answer on Stackoverflow
Solution 2 - JavaStriplingWarriorView Answer on Stackoverflow
Solution 3 - JavaTomas PinosView Answer on Stackoverflow
Solution 4 - JavaTDGView Answer on Stackoverflow
Solution 5 - JavaT.J. CrowderView Answer on Stackoverflow
Solution 6 - Javauser1531971View Answer on Stackoverflow
Solution 7 - JavaAndrew TobilkoView Answer on Stackoverflow
Solution 8 - JavarajuGTView Answer on Stackoverflow
Solution 9 - JavaThierryView Answer on Stackoverflow
Solution 10 - JavaCiaPanView Answer on Stackoverflow
Solution 11 - JavaJoshuaView Answer on Stackoverflow
Solution 12 - JavaKhaled.KView Answer on Stackoverflow
Solution 13 - JavaMasumView Answer on Stackoverflow