54

I have several switch statements which test an enum. All enum values must be handled in the switch statements by a case statement. During code refactoring it can happen that the enum shrinks and grows. When the enum shrinks the compiler throws an error. But no error is thrown, if the the enum grows. The matching state gets forgotten and produces a run time error. I would like to move this error from run time to compile time. Theoretically it should be possible to detect the missing enum cases at compile time. Is there any way to achieve this?

The question exists already "How to detect a new value was added to an enum and is not handled in a switch" but it does not contain an answer only an Eclipse related work around.

Naman
  • 27,789
  • 26
  • 218
  • 353
ceving
  • 21,900
  • 13
  • 104
  • 178
  • 1
    Presumably you don't have a `default` case? – Oliver Charlesworth May 28 '13 at 17:00
  • @OliCharlesworth How do you think I got the run time error? ;-) – ceving May 28 '13 at 17:06
  • 2
    Well, if you have a `default` case, then it's unlikely that any compile-time tool would warn you about "missing" cases (because they're not actually missing in that scenario ;) ) – Oliver Charlesworth May 28 '13 at 17:07
  • @OliCharlesworth I want to get rid of the `default` because this kind of default is always a run time error. Because if it would be a valid state it should have been an enum value. – ceving May 28 '13 at 17:31
  • 4
    Write a test that fails if one case is missing. Run the tests regularly. – assylias May 28 '13 at 19:07
  • Could you throw a RTE on the `default:` case and write a test which inputs all possible enum values? – vikingsteve May 30 '13 at 07:39
  • I would not want this in all cases. Sometimes some values are left out on purpose, or handled in a default. So it is difficult to come up with a useful compile time check without some language extension where the programmer can indicate if a "complete switch" is desired or not. – Henry May 19 '17 at 08:17
  • @assylias that's what compilers are for – still_dreaming_1 Jun 12 '20 at 23:00
  • 2
    @vikingsteve that would require writing tests for something the compiler should catch. Even if someone wanted/tried to do this, what would catch when a test is missing? That is why they are asking this question, and why 7 years later people like me are searching for this kind of thing and finding this question. – still_dreaming_1 Jun 12 '20 at 23:04

13 Answers13

29

In Effective Java, Joshua Bloch recommends creating an abstract method which would be implemented for each constant. For example:

enum Color {
    RED   { public String getName() {return "Red";} },
    GREEN { public String getName() {return "Green";} },
    BLUE  { public String getName() {return "Blue";} };
    public abstract String getName();
}

This would function as a safer switch, forcing you to implement the method if you add a new constant.

EDIT: To clear up some confusion, here's the equivalent using a regular switch:

enum Color {
    RED, GREEN, BLUE;
    public String getName() {
        switch(this) {
            case RED:   return "Red";
            case GREEN: return "Green";
            case BLUE:  return "Blue";
            default: return null;
        }
    }
}
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • It would be useful if you would have shown the corresponding switch. From what you say I got the idea that I have to sub class the original enum in every place where a switch of the enum is used. Is this correct? Sounds like much overhead. – ceving May 29 '13 at 11:28
  • 1
    @ceving No, you can't subclass an `enum`. My assumption was that you have access to the code, in which case it may be preferable to build the switch into the enum itself. See above for an equivalent version implemented as a standard `switch`. – shmosel May 30 '13 at 06:51
  • 9
    Ok this is possible (I tried it and it works) but if I have more than one switch, I move unrelated code from different places in a central `enum` class. It works but the code does not belong there. – ceving Jun 03 '13 at 11:52
13

Another solution uses the functional approach. You just need to declare the enum class according with next template:

public enum Direction {

    UNKNOWN,
    FORWARD,
    BACKWARD;

    public interface SwitchResult {
        public void UNKNOWN();
        public void FORWARD();
        public void BACKWARD();
    }

    public void switchValue(SwitchResult result) {
        switch (this) {
            case UNKNOWN:
                result.UNKNOWN();
                break;
            case FORWARD:
                result.FORWARD();
                break;
            case BACKWARD:
                result.BACKWARD();
                break;
        }
    }
}

If you try to use this without one enumeration constant at least, you will get the compilation error:

getDirection().switchValue(new Direction.SwitchResult() {
    public void UNKNOWN() { /* */ }
    public void FORWARD() { /* */ }
    // public void BACKWARD() { /* */ } // <- Compilation error if missing
});
Ilia Zlobin
  • 146
  • 1
  • 5
  • 2
    This reduces `n` `switch` statements to 1 `switch` statement. It is not a perfect solution but a big improvement. But I think it might be better to call `SwitchResult` `Directable` and `switchValue` `direct`, because I don't think this solution can be abstracted into a template. – ceving Dec 13 '16 at 15:29
11

I don't know about the standard Java compiler, but the Eclipse compiler can certainly be configured to warn about this. Go to Window->Preferences->Java->Compiler->Errors/Warnings/Enum type constant not covered on switch.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • @ceving: Ah ok. You should probably update your question to indicate that you're explicitly interested in `javac` (or whichever compiler) then... – Oliver Charlesworth May 28 '13 at 17:05
  • Yes I had the same idea and added the link the the other question. – ceving May 28 '13 at 17:09
  • @ceving: Indeed, I saw that. But you haven't actually said which compiler you're actually interested in (without this information, your question is just a duplicate of that other one ;) ) – Oliver Charlesworth May 28 '13 at 17:10
  • @ceving: Well, Eclipse comes with its own compiler, which may be invoked standalone from the command-line if required. (Google for "Eclipse ECJ") – Oliver Charlesworth May 28 '13 at 17:17
3

You could also use an adaptation of the Visitor pattern to enums, which avoid putting all kind of unrelated state in the enum class.

The compile time failure will happen if the one modifying the enum is careful enough, but it is not garanteed.

You'll still have a failure earlier than the RTE in a default statement : it will fail when one of the visitor class is loaded, which you can make happen at application startup.

Here is some code :

You start from an enum that look like that :

public enum Status {
    PENDING, PROGRESSING, DONE
}

Here is how you transform it to use the visitor pattern :

public enum Status {
    PENDING,
    PROGRESSING,
    DONE;

    public static abstract class StatusVisitor<R> extends EnumVisitor<Status, R> {
        public abstract R visitPENDING();
        public abstract R visitPROGRESSING();
        public abstract R visitDONE();
    }
}

When you add a new constant to the enum, if you don't forget to add the method visitXXX to the abstract StatusVisitor class, you'll have directly the compilation error you expect everywhere you used a visitor (which should replace every switch you did on the enum) :

switch(anObject.getStatus()) {
case PENDING :
    [code1]
    break;
case PROGRESSING :
    [code2]
    break;
case DONE :
    [code3]
    break;
}

should become :

StatusVisitor<String> v = new StatusVisitor<String>() {
    @Override
    public String visitPENDING() {
        [code1]
        return null;
    }
    @Override
    public String visitPROGRESSING() {
        [code2]
        return null;
    }
    @Override
    public String visitDONE() {
        [code3]
        return null;
    }
};
v.visit(anObject.getStatus());

And now the ugly part, the EnumVisitor class. It is the top class of the Visitor hierarchy, implementing the visit method and making the code fail at startup (of test or application) if you forgot to update the absract visitor :

public abstract class EnumVisitor<E extends Enum<E>, R> {

    public EnumVisitor() {
        Class<?> currentClass = getClass();
        while(currentClass != null && !currentClass.getSuperclass().getName().equals("xxx.xxx.EnumVisitor")) {
            currentClass = currentClass.getSuperclass();
        }

        Class<E> e = (Class<E>) ((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments()[0];
        Enum[] enumConstants = e.getEnumConstants();
        if (enumConstants == null) {
            throw new RuntimeException("Seems like " + e.getName() + " is not an enum.");
        }
        Class<? extends EnumVisitor> actualClass = this.getClass();
        Set<String> missingMethods = new HashSet<>();
        for(Enum c : enumConstants) {
            try {
                actualClass.getMethod("visit" + c.name(), null);
            } catch (NoSuchMethodException e2) {
                missingMethods.add("visit" + c.name());
            } catch (Exception e1) {
                throw new RuntimeException(e1);
            }
        }
        if (!missingMethods.isEmpty()) {
            throw new RuntimeException(currentClass.getName() + " visitor is missing the following methods : " + String.join(",", missingMethods));
        }
    }

    public final R visit(E value) {
        Class<? extends EnumVisitor> actualClass = this.getClass();
        try {
            Method method = actualClass.getMethod("visit" + value.name());
            return (R) method.invoke(this);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

There are several ways you could implement / improve this glue code. I choose to walk up the class hierarchy, stop when the superclass is the EnumVisitor, and read the parameterized type from there. You could also do it with a constructor param being the enum class.

You could use a smarter naming strategy to have less ugly names, and so on...

The drawback is that it is a bit more verbose. The benefits are

  • compile time error [in most cases anyway]
  • works even if you don't own the enum code
  • no dead code (the default statement of switch on all enum values)
  • sonar/pmd/... not complaining that you have a switch statement without default statement
Thierry
  • 5,270
  • 33
  • 39
3

The Enum Mapper project provides an an annotation processor which will make sure at compile-time that all enum constants are handled.
Moreover it supports reverse lookup and paritial mappers.

Usage example:

@EnumMapper
public enum Seasons {
  SPRING, SUMMER, FALL, WINTER
}

The annotation processor will generate a java class Seasons_MapperFull, which can be used to map all enum constants to arbitrary values.

Here is an example use where we map each enum constant to a string.

EnumMapperFull<Seasons, String> germanSeasons = Seasons_MapperFull
     .setSPRING("Fruehling")
     .setSUMMER("Sommer")
     .setFALL("Herbst")
     .setWINTER("Winter");

You can now use the mapper to get the values, or do reverse lookup

String germanSummer = germanSeasons.getValue(Seasons.SUMMER); // returns "Sommer"
ExtremeSeasons.getEnumOrNull("Sommer");                 // returns the enum-constant SUMMER
ExtremeSeasons.getEnumOrRaise("Fruehling");             // throws an IllegalArgumentException 
TmTron
  • 17,012
  • 10
  • 94
  • 142
2

Probably a tool like FindBugs will mark such switches.

The hard answer would be to refactor:

Possibility 1: can go Object Oriented

If feasible, depends on the code in the cases.

Instead of

switch (language) {
case EO: ... break;
case IL: ... break;
}

create an abstract method:, say p

language.p();

or

switch (p.category()) {
case 1: // Less cases.
...
}

Possibility 2: higher level

When having many switches, in an enum like DocumentType, WORD, EXCEL, PDF, ... . Then create a WordDoc, ExcelDoc, PdfDoc extending a base class Doc. And again one can work object oriented.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • I think in your possibility 1 it is still possible to miss a "category" at compile time. – ceving May 28 '13 at 17:20
  • 1
    It's probably worth mentioning that `p()` would need to be marked `abstract` in the base class. – Oliver Charlesworth May 28 '13 at 17:21
  • What does this help? If `category()` can return 1, 3, and 5 who checks that the switch contains for 1, 3 and 5 a matching case expression? – ceving May 28 '13 at 17:23
  • (1) If an implementation is indeed needed, yes **abstract** - thanks @OliCharlesworth. It depends on whether normally a switch is added or an enum value is added. (2) If many cases are the same, having a switch with say just 3 categories is less error prone, and the number of categories will not grow as fast if ever. _All this is merely a software engineering code style argumentation. Whether sensible depends._ – Joop Eggen May 28 '13 at 17:38
2

In my opinion and if the code that your are going to execute is outside of the domain of your enum, a way to do that is to build a unit test case that loops through your items in the enumeration and execute the piece of code that contains the switch.If something goes wrong or not as expected you can check the return value or the state of the object with an assertion.

You could execute the tests as part of some building process and you will see any anomalies at this point.

Anyway, unit testing is almost mandatory and beneficial in many projects.

If the code inside the switch belongs in the enum, include it within as proposed in other answers.

Víctor Herraiz
  • 1,132
  • 1
  • 11
  • 26
1

If you're using Android Studio (at least version 3 and up) you can activate this exact check in the inspections setting. This might be available on other IntelliJ Java IDE's as well.

Go to Preferences/Inspections. In the Java/Control flow Issues section, check the item Enum 'switch' statement that misses case. Optionally you can change severity to Error to make it more obvious than a warning.

Magnus
  • 17,157
  • 19
  • 104
  • 189
1

I know the question is about Java, and I think the answer for pure Java is clear: it's not a built-in feature, but there are workarounds. For those who arrive here and are working on Android or other systems that can utilize Kotlin, that language provides this feature with its when expression, and the interop with Java allows it to be rather seamless, even if this is the only Kotlin code in your codebase.

For example:

public enum HeaderSignalStrength {
  STRENGTH_0, STRENGTH_1, STRENGTH_2, STRENGTH_3, STRENGTH_4;
}

With my original Java code as:

// In HeaderUtil.java
@DrawableRes
private static int getSignalStrengthIcon(@NonNull HeaderSignalStrength strength) {
  switch (strength) {
    case STRENGTH_0: return R.drawable.connection_strength_0;
    case STRENGTH_1: return R.drawable.connection_strength_1;
    case STRENGTH_2: return R.drawable.connection_strength_2;
    case STRENGTH_3: return R.drawable.connection_strength_3;
    case STRENGTH_4: return R.drawable.connection_strength_4;
    default:
      Log.w("Unhandled HeaderSignalStrength: " + strength);
      return R.drawable.cockpit_connection_strength_0;
  }
}

// In Java code somewhere
mStrength.setImageResource(HeaderUtil.getSignalStrengthIcon(strength));

Can be rewritten with Kotlin:

// In HeaderExtensions.kt
@DrawableRes
fun HeaderSignalStrength.getIconRes(): Int {
    return when (this) {
        HeaderSignalStrength.STRENGTH_0 -> R.drawable.connection_strength_0
        HeaderSignalStrength.STRENGTH_1 -> R.drawable.connection_strength_1
        HeaderSignalStrength.STRENGTH_2 -> R.drawable.connection_strength_2
        HeaderSignalStrength.STRENGTH_3 -> R.drawable.connection_strength_3
        HeaderSignalStrength.STRENGTH_4 -> R.drawable.connection_strength_4
    }
}

// In Java code somewhere
mStrength.setImageResource(HeaderExtensionsKt.getIconRes(strength));
Jeff DQ
  • 458
  • 1
  • 4
  • 11
1

Had the same issue. I throw an error on the default case and add a static initializer that iterates all enum values. Simple but fails fast. If you have some unit test coverage it does the trick.

public class HolidayCalculations {
    
    public static Date getDate(Holiday holiday, int year) {
        switch (holiday) {
        case AllSaintsDay:
        case AscensionDay:
            return new Date(1);
        default: 
            throw new IllegalStateException("getDate(..) for "+holiday.name() + " not implemented");
            
        }
    }
    
    static {
        for (Holiday value : Holiday.values()) getDate(value, 2000);
    }
    
}
0

This is a variant of the Visitor approach which gives you compile-time help when you add constants:

interface Status {
    enum Pending implements Status {
        INSTANCE;

        @Override
        public <T> T accept(Visitor<T> v) {
            return v.visit(this);
        }
    }
    enum Progressing implements Status {
        INSTANCE;

        @Override
        public <T> T accept(Visitor<T> v) {
            return v.visit(this);
        }
    }
    enum Done implements Status {
        INSTANCE;

        @Override
        public <T> T accept(Visitor<T> v) {
            return v.visit(this);
        }
    }

    <T> T accept(Visitor<T> v);
    interface Visitor<T> {
        T visit(Done done);
        T visit(Progressing progressing);
        T visit(Pending pending);
    }
}

void usage() {
    Status s = getRandomStatus();
    String userMessage = s.accept(new Status.Visitor<String>() {
        @Override
        public String visit(Status.Done done) {
            return "completed";
        }

        @Override
        public String visit(Status.Progressing progressing) {
            return "in progress";
        }

        @Override
        public String visit(Status.Pending pending) {
            return "in queue";
        }
    });
}

Beautiful, eh? I call it the "Rube Goldberg Architecture Solution".

I would normally just use an abstract method, but if you really don't want to add methods to your enum (maybe because you introduce cyclic dependencies), this is a way.

Alexander Torstling
  • 18,552
  • 7
  • 62
  • 74
  • I am not sure, if I understand this. Is this one enum with three values or three enums with one value? – ceving Dec 13 '16 at 15:32
0

Functional approach with lambdas, much less code

public enum MyEnum {
    FIRST,
    SECOND,
    THIRD;

    <T> T switchFunc(
            Function<MyEnum, T> first,
            Function<MyEnum, T> second,
            Function<MyEnum, T> third
            // when another enum constant is added, add another function here
            ) {
        switch (this) {
            case FIRST: return first.apply(this);
            case SECOND: return second.apply(this);
            case THIRD: return third.apply(this);
            // and case here
            default: throw new IllegalArgumentException("You forgot to add parameter");
        }
    }

    public static void main(String[] args) {
        MyEnum myEnum = MyEnum.FIRST;

        // when another enum constant added method will break and trigger compile-time error
        String r = myEnum.switchFunc(
                me -> "first",
                me -> "second",
                me -> "third");
        System.out.println(r);
    }

}

JackHammer
  • 458
  • 1
  • 3
  • 16
-1

In case there are several enums on different tiers of the project that must correspond to each other, this can be ensured by a test case:

private static <T extends Enum<T>> String[] names(T[] values) {
    return Arrays.stream(values).map(Enum::name).toArray(String[]::new);
}

@Test
public void testEnumCompleteness() throws Exception {
    Assert.assertArrayEquals(names(Enum1.values()), names(Enum2.values()));
}
Vadzim
  • 24,954
  • 11
  • 143
  • 151