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 をクラッシュさせたいのですが、それは簡単ではありません。catch(Throwable e)aはすべてをキャッチするため、例外のスローは効果的ではありません。

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 holder イディオムを使用して、インスタンスが初期化される前に初期化されたセットを提供できます。

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 に答える