枚举参数的编译时验证

时间:2012-10-16 15:15:27

标签: java validation enums compile-time

有一个带有三个enum类型参数的构造函数:

public SomeClass(EnumType1 enum1,EnumType2 enum2, EnumType3 enum3)
{...}

类型枚举的三个参数不允许与所有可能的值组合使用:

示例:

  

EnumType1.VALUE_ONE,EnumType2.VALUE_SIX,EnumType3.VALUE_TWENTY是有效组合。

但以下组合无效:

  

EnumType1.VALUE_TWO,EnumType2.VALUE_SIX,EnumType3.VALUE_FIFTEEN

每个EnumTypes知道允许组合哪些值:

EnumType1和另外两个实现了一个isAllowedWith()方法来检查如下:

public enum EnumType1 {

VALUE_ONE,VALUE_TWO,...;

    public boolean isAllowedWith(final EnumType2 type) {
    switch (this) {
        case VALUE_ONE:
            return type.equals(Type.VALUE_THREE);
        case VALUE_TWO:
            return true;
        case VALUE_THREE:
            return type.equals(Type.VALUE_EIGHT);
        ...
    }
}

我需要在编译时运行该检查,因为在我的项目中,组合在运行时始终是正确的,这是非常重要的。

我想知道是否有可能使用用户定义的注释运行该检查?

赞赏每一个想法:)

5 个答案:

答案 0 :(得分:4)

上面的帖子没有带来编译时检查的解决方案,这是我的:

为什么不使用嵌套 Enum的概念。

您会EnumType1包含自己的值+嵌套EnumType2,而这一个是嵌套的EnumType3

您可以使用有用的组合来整理整个内容。 最终可能会有3个类(EnumType1,2和3),每个类都包含其他值,其中包含其他具有允许关联值的值。

您的通话看起来就是这样(假设您希望EnumType1.VALUE_ONEEnumType2.VALUE_FIFTEEN相关联):

EnumType1.VALUE_ONE.VALUE_FIFTEEN  //second value corresponding to EnumType2

因此,你也可以:EnumType3.VALUE_SIX.VALUE_ONE(其中SIX由type3知道,ONE由type1知道)。

您的电话会改为:

public SomeClass(EnumType1 enumType)

=>样本:

SomeClass(EnumType1.VALUE_ONE.VALUE_SIX.VALUE_TWENTY) //being a valid combination as said

为了更好地澄清它,请查看此帖:Using nested enum types in Java

答案 1 :(得分:3)

所以最简单的方法是1)定义文档来解释有效组合和
2)在构造函数中添加检查

如果构造函数抛出异常而不是调用者的责任。基本上你会做这样的事情:

public MyClass(enum foo, enum bar, enum baz)  
{  
    if(!validateCombination(foo,bar,baz))
    {  
        throw new IllegalStateException("Contract violated");
    }  
} 


private boolean validateCombination(enum foo, enum bar, enum baz)  
{  
    //validation logic
} 

现在这部分绝对是至关重要的。将类标记为final,可能会恢复并滥用部分构造的对象来破坏您的应用程序。对于标记为final的类,恶意程序无法扩展部分构造的对象并造成严重破坏。

答案 2 :(得分:2)

另一个想法是编写一些自动化测试来捕获它,并在打包/部署应用程序之前将它们作为必修步骤挂钩到构建过程中。

如果您考虑一下您要在此处捕获的内容,那么代码是合法错误。虽然你可以在编译阶段捕捉到这一点,但这正是测试的意义所在。

这符合您无法使用非法组合构建任何代码的要求,因为构建仍然会失败。并且可以说,除了编写自己的注释处理器之外,其他开发人员更容易理解......

答案 3 :(得分:1)

我知道的唯一方法是使用注释。

这就是我的意思。 现在你的构造函数接受3个参数:

public SomeClass(EnumType1 enum1,EnumType2 enum2, EnumType3 enum3){}

所以你称之为:

SomeClass obj = new SomeClass(EnumTupe1.VALUE1, EnumTupe2.VALUE2, EnumTupe1.VALUE3)

将构造函数更改为私有。创建接受所需任何类型的1个参数的公共构造函数。它可能只是一个假参数。

public SomeClass(Placeholder p)

现在,您需要调用此构造函数,而每个参数都使用特殊注释进行注释。我们称之为TypeAnnotation

SomeClass obj = new SomeClass(TypeAnnotation(
    type1=EnumType1.VALUE1, 
    type2=EnumTupe2.VALUE2,
    type3=EnumTupe1.VALUE3)
    p3);

调用更冗长,但这是我们必须为编译时验证付出的代价。

现在,如何定义注释?

@Documented @Retention({RetentionPolicy.RUNTIME,RetentionPolicy.SOURCE}) @target(PARAMETER) @interface TypeAnnotation {     EnumType1 type1();     EnumType2 type3();     EnumType3 type3(); }

请注意目标是PARAMETER,保留值是RUNTIME和SOURCE。

RUNTIME允许在运行时读取此注释,而SOURCE允许创建可在运行时验证参数的注释处理器。

现在公共构造函数将调用3参数private construcor:

public SomeClass(Placeholder p){     this(readAnnotation(EnumType1.class),readAnnotation(EnumType2.class),readAnnotation(EnumType3.class),) }

我这里没有实现readAnnotation():它应该是静态方法,它接受堆栈跟踪,返回3个元素(对公共构造函数的调用者)并解析注释TypeAnnotation

现在是最有趣的部分。您必须实现注释处理器。 请查看here以获取说明,并here查看注释处理器的示例。

您必须将此批注处理器的用法添加到构建脚本和(可选)IDE的用法。在这种情况下,当违反兼容性规则时,您将收到真正的编译错误。

我相信这个解决方案看起来太复杂了,但如果你真的需要这个,你就可以做到这一点。可能需要一天左右的时间。祝你好运。

答案 4 :(得分:0)

好吧,我不知道编译时间检查但我不认为这是可能的,因为编译器如何知道将哪个值传递给构造函数(如果你的枚举变量的值是在运行时计算的(例如,通过If子句)? 这只能在运行时通过使用为枚举类型实现的验证器方法进行验证。

示例:

如果您的代码中有这样的内容:

EnumType1 enumVal;

if (<some condition>) {
  enumVal = EnumType2.VALUE_SIX;
} else {
  enumVal = EnumType2.VALUE_ONE;
}

编译器无法知道将哪个值分配给enumVal,因此在if块被评估之前,它无法验证传递给构造函数的内容(这只能在运行时完成)