How to ensure completeness in an enum switch at compile time?

后端 未结 12 792
悲&欢浪女
悲&欢浪女 2020-11-27 06:25

I have several switch statements which test an enum. All enum values must be handled in the switch statements by a case s

相关标签:
12条回答
  • 2020-11-27 06:29

    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.

    0 讨论(0)
  • 2020-11-27 06:32

    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 
    
    0 讨论(0)
  • 2020-11-27 06:33

    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 safe 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;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-27 06:37

    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.

    0 讨论(0)
  • 2020-11-27 06:42

    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()));
    }
    
    0 讨论(0)
  • 2020-11-27 06:49

    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
    });
    
    0 讨论(0)
提交回复
热议问题