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

JavaSwitch Statement

Java Problem Overview


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 "https://stackoverflow.com/questions/3999942/how-to-detect-a-new-value-was-added-to-an-enum-and-is-not-handled-in-a-switch?rq=1" but it does not contain an answer only an Eclipse related work around.

Java Solutions


Solution 1 - Java

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;
        }
    }
}

Solution 2 - Java

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
});

Solution 3 - Java

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.

Solution 4 - Java

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

Solution 5 - Java

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 

Solution 6 - Java

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.

Solution 7 - Java

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.

Solution 8 - Java

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.

Solution 9 - Java

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));

Solution 10 - Java

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.

Solution 11 - Java

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);
	}

}

Solution 12 - Java

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);
	}
	
}

Solution 13 - Java

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()));
}

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
QuestioncevingView Question on Stackoverflow
Solution 1 - JavashmoselView Answer on Stackoverflow
Solution 2 - JavaIlia ZlobinView Answer on Stackoverflow
Solution 3 - JavaOliver CharlesworthView Answer on Stackoverflow
Solution 4 - JavaThierryView Answer on Stackoverflow
Solution 5 - JavaTmTronView Answer on Stackoverflow
Solution 6 - JavaJoop EggenView Answer on Stackoverflow
Solution 7 - JavaVíctor HerraizView Answer on Stackoverflow
Solution 8 - JavaMagnusView Answer on Stackoverflow
Solution 9 - JavaJeff DQView Answer on Stackoverflow
Solution 10 - JavaAlexander TorstlingView Answer on Stackoverflow
Solution 11 - JavaJackHammerView Answer on Stackoverflow
Solution 12 - JavaBruno WengerView Answer on Stackoverflow
Solution 13 - JavaVadzimView Answer on Stackoverflow