2

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

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 崩溃,但这并不容易。抛出异常无效,因为 acatch(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");
        }
    }
}

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

4

3 回答 3

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

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

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

您可以使用Initialization-on-demand 持有者习语在实例初始化之前为您提供一个初始化集:

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 类首先在构造函数中使用,然后类加载器初始化集合。

要查看访问枚举时会发生什么,请参阅此链接

于 2013-08-31T12:27:21.077 回答
2

几点:

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

我将编写验证代码如下:

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

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

于 2013-08-31T04:19:45.993 回答
0

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

于 2013-08-31T02:14:09.590 回答