enum 定数の switch 文の実装
switch 文の case ラベルが int や short のプリミティブ型の final 定数であれば、その即値が switch 文にインライン展開される。
したがって、定数の値が変更されたら、その switch 文を使うクライアントコードは再コンパイルが必要になる。
ところが、列挙クラスのソースコードを修正して列挙定数の序数が変わった場合、switch 文にその列挙定数を使っているクライアントコードは再コンパイルする必要はない。
これが不思議だったので、バイトコードを調べてみた。
まず、以下、検証用のサンプルコードである。
// Planet.java public enum Planet { SUN, MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN }
クライアントコードは以下のとおり。
// Main.java public class Main { public static void main(String[] args) { switch (Planet.EARTH) { case JUPITER: System.out.println("Jupiter"); break; case VENUS: System.out.println("Venus"); break; case EARTH: System.out.println("Earth"); break; } } }
クライアントコードの Main.java をコンパイルすると、バイトコードに static 配列が追加される。実行時、この配列には列挙定数の序数と case ラベルのマッピングデータが動的に格納される。配列は、すべての列挙定数を格納できるサイズで作成されるが、マッピングデータは case ラベルに使用されている定数のものだけが格納される。
以下は、バイトコードとほぼ等価な Java コードである(例外処理は省略)
public class Main { // コンパイラは、列挙定数の序数からcaseラベルへのマッピングのための配列を追加する. static final int $Map[]; static { // マッピング用の配列をインスタンス化する.サイズはすべての列挙定数の個数. $Map = new int[Planet.values().length]; // switch 文のラベルに記されている順番で 1 から付番してマッピング用のデータを作成する. $Map[Planet.JUPITER.ordinal()] = 1; $Map[Planet.VENUS.ordinal()] = 2; $Map[Planet.EARTH.ordinal()] = 3; } public static void main(String[] args) { int ordinal = Planet.EARTH.ordinal(); int index = $Map[ordinal]; switch(index) { case 1: System.out.println("Jupiter"); break; case 2: System.out.println("Venus"); break; case 3: System.out.println("Earth"); break; } } }
バイトコードの解析には、jad コマンドを利用した。
以下は、コマンド jad Main.class で逆コンパイルしたコードである。
case ラベルに使用されている列挙定数の取得に失敗したとき、例外を無視している。
これによって、列挙クラスで当該の列挙定数が削除された後も switch 文は問題なく動作する。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: Main.java import java.io.PrintStream; public class Main { public Main() { } public static void main(String args[]) { static class _cls1 { static final int $SwitchMap$Planet[]; static { $SwitchMap$Planet = new int[Planet.values().length]; try { $SwitchMap$Planet[Planet.JUPITER.ordinal()] = 1; } catch(NoSuchFieldError nosuchfielderror) { } try { $SwitchMap$Planet[Planet.VENUS.ordinal()] = 2; } catch(NoSuchFieldError nosuchfielderror1) { } try { $SwitchMap$Planet[Planet.EARTH.ordinal()] = 3; } catch(NoSuchFieldError nosuchfielderror2) { } } } switch(_cls1..SwitchMap.Planet[Planet.EARTH.ordinal()]) { case 1: // '\001' System.out.println("Jupiter"); break; case 2: // '\002' System.out.println("Venus"); break; case 3: // '\003' System.out.println("Earth"); break; } } }
コマンド jad -dis Main.class で逆コンパイルしたコードは以下のとおり。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) disassembler // Source File Name: Main.java import java.io.PrintStream; public class Main { public Main() { // 0 0:aload_0 // 1 1:invokespecial #1 <Method void Object()> // 2 4:return } public static void main(String args[]) { // 0 0:getstatic #2 <Field int[] Main$1.$SwitchMap$Planet> // 1 3:getstatic #3 <Field Planet Planet.EARTH> // 2 6:invokevirtual #4 <Method int Planet.ordinal()> // 3 9:iaload // 4 10:tableswitch 1 3: default 66 // 1 36 // 2 47 // 3 58 // 5 36:getstatic #5 <Field PrintStream System.out> // 6 39:ldc1 #6 <String "Jupiter"> // 7 41:invokevirtual #7 <Method void PrintStream.println(String)> // 8 44:goto 66 // 9 47:getstatic #5 <Field PrintStream System.out> // 10 50:ldc1 #8 <String "Venus"> // 11 52:invokevirtual #7 <Method void PrintStream.println(String)> // 12 55:goto 66 // 13 58:getstatic #5 <Field PrintStream System.out> // 14 61:ldc1 #9 <String "Earth"> // 15 63:invokevirtual #7 <Method void PrintStream.println(String)> // 16 66:return } // Unreferenced inner class Main$1 /* anonymous class */ static class _cls1 { static { // 0 0:invokestatic #1 <Method Planet[] Planet.values()> // 1 3:arraylength // 2 4:newarray int[] // 3 6:putstatic #2 <Field int[] $SwitchMap$Planet> // try 9 20 handler(s) 23 // 4 9:getstatic #2 <Field int[] $SwitchMap$Planet> // 5 12:getstatic #3 <Field Planet Planet.JUPITER> // 6 15:invokevirtual #4 <Method int Planet.ordinal()> // 7 18:iconst_1 // 8 19:iastore // 9 20:goto 24 // catch NoSuchFieldError // 10 23:astore_0 // try 24 35 handler(s) 38 // 11 24:getstatic #2 <Field int[] $SwitchMap$Planet> // 12 27:getstatic #6 <Field Planet Planet.VENUS> // 13 30:invokevirtual #4 <Method int Planet.ordinal()> // 14 33:iconst_2 // 15 34:iastore // 16 35:goto 39 // catch NoSuchFieldError // 17 38:astore_0 // try 39 50 handler(s) 53 // 18 39:getstatic #2 <Field int[] $SwitchMap$Planet> // 19 42:getstatic #7 <Field Planet Planet.EARTH> // 20 45:invokevirtual #4 <Method int Planet.ordinal()> // 21 48:iconst_3 // 22 49:iastore // 23 50:goto 54 // catch NoSuchFieldError // 24 53:astore_0 // 25 54:return } static final int $SwitchMap$Planet[]; } }
この話の詳細については、次の記事を参照。
Java列挙型メモ(Hishidama's Java enum Memo)