【问题标题】:Compiletime validation of enum parameters枚举参数的编译时验证
【发布时间】:2012-10-06 17:50:10
【问题描述】:

有一个构造函数,带有三个枚举类型的参数:

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

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

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

每一个想法都值得赞赏:)

【问题讨论】:

  • 这绝对是您可以并且应该使用apt 做的事情,尽管我对它不够熟悉,无法写出合格的答案。

标签: java validation enums compile-time


【解决方案1】:

因此,最简单的方法是 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 的类,恶意程序无法扩展部分构造的对象并造成严重破坏。

【讨论】:

  • 但这仍然不是编译时检查
  • IllegalArgumentException 而不是 IllegalStateException 呢?
  • @BheshGurung 从技术上讲,该程序处于非法状态,而不是非法参数。
  • @giorashc 我知道这不是编译时检查,但操作员说“每个想法都值得赞赏:)”
  • @SixDoubleFiveTreeTwoOne 所以问题是为什么在对象实例化时恢复为时已晚?对我来说,这意味着有一些不相交的系统会喷出你必须处理的垃圾。让我知道,因为这可能不需要在编译时处理。
【解决方案2】:

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

例子:

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

EnumType1 enumVal;

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

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

【讨论】:

    【解决方案3】:

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

    为什么不使用嵌套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

    【讨论】:

    • 但是你仍然可以做 EnumType1.VALUE_TWO.VALUE_SIX.VALUE_FIFTEEN 这在 OP 的情况下是非法的
    • 不,你不能。 VALUE_FIFTEEN 不会包含在其嵌套枚举中。我只是给出概念,我选择的值是为了示例。
    • 好的,知道了。但这仍然是一个非常麻烦的枚举:)
    • @giorashc 当然是的 :) 但是除非 Java 中存在超级关键字列表,否则它是我可以带来的第一个也是最后一个解决方案。实际上,这将是一个神圣的迷宫,但它会起作用,正如我们在法语中所说的“Qui netente rien n'a rien”;)
    • @Mik378 我认为这是个好主意。问题是,我无法更改枚举类型(第三方提供)。现在我想我必须重新设计这部分应用程序。
    【解决方案4】:

    另一种想法是编写一些自动化测试来捕捉这一点,并将它们作为打包/部署应用程序之前的强制性步骤挂钩到您的构建过程中。

    如果您考虑一下您要在这里捕获的内容,那是合法错误的代码。虽然您可以在编译阶段发现这一点,但这正是测试的目的。

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

    【讨论】:

    • 你假设正确的测试是正确的......我们已经这样做了。以这种方式发现这种逻辑错误的代码实在是太不雅了。
    • 我认为无论哪种方式,您都可以提出理由,因为在您的情况下,开发人员将编写有效的 Java,这在您的应用程序上下文中没有意义/做正确的事情。这大致就是测试要捕获的内容。 (虽然我也喜欢很好的编译器检查,所以我不能怪你寻找替代品。)
    【解决方案5】:

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

    这就是我的意思。 现在您的构造函数接受 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);
    

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

    现在,如何定义注解?

    @记录 @Retention({RetentionPolicy.RUNTIME, RetentionPolicy.SOURCE}) @目标(参数) @interface 类型注解 { 枚举类型1 type1(); EnumType2 type3(); 枚举类型3 type3(); }

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

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

    现在公共构造函数将调用 3 参数私有构造函数:

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

    我没有在这里实现readAnnotation():它应该是获取堆栈跟踪、返回 3 个元素(返回到公共 costructor 的调用者)并解析注释 TypeAnnotation 的静态方法。

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

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

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

    【讨论】:

    • 谢谢。我会看看这个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多