如何验证java枚举的状态?

时间:2013-08-31 02:02:06

标签: java

我希望能够验证枚举的状态,以确保没有重复的代码。例如,考虑下面的枚举。

public enum UniqueCodes {
    A(1), B(2), C(3), D(1);

    private final int value;

    static {
        UniqueCodes[] values = UniqueCodes.values();
        Map<Integer, Boolean> map = new HashMap<>();
        for (UniqueCodes code : values) {

            if (map.get(code.value) == null) {
                map.put(code.value, true);
            } else {
                String msg = String.format(
                        "%s enum contains a non unique code %s",
                        UniqueCodes.class.getName(), code.value);
                System.err.println(msg);

                try {
                    System.exit(-1);
                } catch(SecurityException e) {
                    System.err.println("Really Bad things are going to happen to the application");
                    // what can I do here to crash the JVM 
                }
            }

        }
    }

    private UniqueCodes(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

想象一下上面的枚举,分配了100多个代码,并且您希望确保没有枚举定义 包含重复值。如果检测到重复值,我想让JVM崩溃,但这并不容易。抛出异常是无效的,因为catch(Throwable e)将捕获所有内容。

public class Main {
    public static void main(String[] args) {
        try { 
            System.out.println(UniqueCodes.A);
        } catch(Throwable e) {
            System.out.println("Invalid Enum exception caught");
        }
    }
}

我可以编写一个单元测试来证明枚举定义是好的并且没有重复的代码。但有没有办法让它自我测试和傻瓜证明,以便如果枚举没有唯一代码,事情就不会运行?

3 个答案:

答案 0 :(得分:2)

有几点:

  1. 使用集合比使用地图更简单。
  2. 从类的静态块中抛出异常将有效,因为它会阻止类的加载。即使您故意捕获并忽略catch (Throwable t)的第一个错误,任何试图使用“无效”枚举的后续代码都会自发地抛出java.lang.NoClassDefFoundError
  3. 我会按如下方式编写验证码:

    static {
        Set<Integer> set = new HashSet<>();
        for (UniqueCodes code : values()) {
            if (!set.add(code.value)) {
                throw new RuntimeException(String.format(
                    "%s enum contains a non unique code %s",
                    UniqueCodes.class.getName(), code.value));
            }
        }
    }
    

    P.S。如果您对唯一代码不需要任何特定值,则应该知道Enum.ordinal()存在,它按照定义的顺序返回常量的从零开始的索引。 / p>

答案 1 :(得分:2)

最简单的方法是让构造函数检查该值是否唯一,如下所示:

A(1), B(2), C(3), D(1);
// Not initialized until after instances
private static Set<Integer> set = new HashSet<Integer>();
private final int value;
private UniqueCodes(int value) {
    // throws NPE
    if (!set.add(value))
        throw new IllegalArgumentException("Duplicate value: " + value);
    this.value = value;
}

但是enums面临的挑战是静态字段必须出现在实例之后,因此在之后所有构​​造函数执行后才会初始化 - 太晚了,当你去使用时你会得到一个NPE集合。

幸运的是,有一个解决方法!

您可以使用Initialization-on-demand holder idiom初始化实例之前为您提供初始化的集合

public enum UniqueCodes {
    A(1), B(2), C(3), D(1);
    private static class Holder {
        static Set<Integer> set = new HashSet<Integer>();
    }
    private final int value;
    private UniqueCodes(int value) {
        if (!Holder.set.add(value))
            throw new IllegalArgumentException("Duplicate value: " + value);
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

这个工作的原因是由于类加载器契约,它必须在使用类之前初始化所有静态字段,并在首次使用时加载该类。 Holder类首先在构造函数中使用,此时类加载器初始化集合。

要查看访问枚举时会发生什么,请参阅this link

答案 2 :(得分:0)

我不确定这对于像你的例子这样的枚举是否值得。既然你自己硬编码枚举,你不能像编码器那样确保你没有错误地对枚举进行硬编码吗?