如何创建枚举新增值的编译时校验规则?限制sequential后不可加parallel
编译阶段约束枚举新增值的校验方案
核心需求
当枚举中已存在带"sequential"参数的枚举值后,禁止添加带"parallel"参数的新枚举值。例如现有枚举:
public enum Execution { TAKS_N("parallel"), TASK_A("sequential"), TASK_D("sequential") }
允许在sequential值之前添加新的parallel值,但禁止在sequential之后添加parallel值。
编译阶段实现方案
单元测试属于运行时校验,若要在编译阶段直接拦截违规操作,可使用Java注解处理器(Annotation Processor)——这是Java官方提供的编译期扩展机制,能在编译时扫描、处理注解,对代码结构做校验,不符合规则直接抛出编译错误。
步骤1:自定义校验注解
先定义一个注解,用来标记需要校验的枚举:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface SequentialFirstConstraint { // 指定枚举构造方法中用来区分类型的参数索引(这里是第一个参数,默认值为0) int typeParamIndex() default 0; // 触发禁止规则后不允许新增的参数值 String forbiddenValue() default "parallel"; // 用来触发禁止规则的参数值 String triggerValue() default "sequential"; }
步骤2:实现注解处理器
编写注解处理器逻辑,在编译时扫描标记了@SequentialFirstConstraint的枚举,检查枚举常量的顺序和参数:
@SupportedAnnotationTypes("com.yourpackage.SequentialFirstConstraint") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class SequentialConstraintProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement annotation : annotations) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation); for (Element element : elements) { if (element.getKind() == ElementKind.ENUM) { checkEnumConstraint((TypeElement) element); } } } return true; } private void checkEnumConstraint(TypeElement enumElement) { SequentialFirstConstraint constraint = enumElement.getAnnotation(SequentialFirstConstraint.class); int paramIndex = constraint.typeParamIndex(); String forbiddenValue = constraint.forbiddenValue(); String triggerValue = constraint.triggerValue(); boolean triggerFound = false; // 遍历所有枚举常量 for (Element enumConstant : enumElement.getEnclosedElements()) { if (enumConstant.getKind() == ElementKind.ENUM_CONSTANT) { // 获取枚举常量的构造方法参数 ExecutableElement constructor = (ExecutableElement) enumConstant.getEnclosedElements().stream() .filter(e -> e.getKind() == ElementKind.CONSTRUCTOR) .findFirst() .orElseThrow(() -> new IllegalStateException("Enum constant has no constructor")); // 获取指定索引的参数值(假设为字符串常量) if (constructor.getParameters().size() > paramIndex) { Expression valueExpr = ((VariableElement) constructor.getParameters().get(paramIndex)).getInitializer(); if (valueExpr instanceof StringLiteralExpr) { String paramValue = ((StringLiteralExpr) valueExpr).getValue(); if (triggerValue.equals(paramValue)) { triggerFound = true; } else if (triggerFound && forbiddenValue.equals(paramValue)) { // 抛出编译错误 processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "禁止在已存在'" + triggerValue + "'类型的枚举值后添加'" + forbiddenValue + "'类型的新值", enumConstant ); } } } } } } }
步骤3:注册注解处理器
在项目的META-INF/services/javax.annotation.processing.Processor文件中添加处理器的全类名:
com.yourpackage.SequentialConstraintProcessor
如果用Maven,也可以通过maven-compiler-plugin配置,或者引入com.google.auto.service:auto-service依赖,用@AutoService注解简化注册流程。
步骤4:在目标枚举上应用注解
将注解加到需要校验的枚举类上:
@SequentialFirstConstraint public enum Execution { TAKS_N("parallel"), TASK_ALLOWED("parallel"), // 允许,此时还未出现sequential类型 TASK_A("sequential"), TASK_D("sequential"), TASK_NOT_ALLOWED("parallel") // 编译时直接报错! }
替代方案:自定义Lint规则
如果是Android项目,也可以自定义Lint规则,逻辑和注解处理器类似,但Lint更偏向代码规范检查,同样能在编译阶段抛出错误。不过注解处理器是通用Java项目都支持的方案。
方案对比
- 单元测试:仅在运行时校验,存在漏检风险(比如未执行测试就提交代码)。
- 注解处理器:编译阶段直接拦截,强制符合规则,从源头避免违规代码流入代码库。
内容的提问来源于stack exchange,提问作者Moritz Kampfinger
相关产品推荐
相关产品推荐

