argius note

プログラミング関連

Java SE 7 (1) - 文字列switchのからくり

遅れ馳せ乍ら、Java7について自分なりにまとめてみます。

最初は概論から入るのが定石でありましょうが、現状は何時投稿できるか分からない状況ですので、小出しにしていくことにします。



まずは、"Strings in switch Statements"から。
これは珍しくピーンと来たので、確認をしてみます。

final class Main {

    void f(String s) {
        switch (s) {
            case "aa":
                System.out.println("1");
                break;
            case "bb":
                System.out.println("2");
                break;
            default:
        }
    }

}

これをコンパイルして、javapでディスアセンブルしてみます。

Compiled from "1.java"
final class Main {
  Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void f(java.lang.String);
    Code:
       0: aload_1
       1: astore_2
       2: iconst_m1
       3: istore_3
       4: aload_2
       5: invokevirtual #2                  // Method java/lang/String.hashCode:()I
       8: lookupswitch  { // 2
                  3104: 36
                  3136: 50
               default: 61
          }
      36: aload_2
      37: ldc           #3                  // String aa
      39: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      42: ifeq          61
      45: iconst_0
      46: istore_3
      47: goto          61
      50: aload_2
      51: ldc           #5                  // String bb
      53: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      56: ifeq          61
      59: iconst_1
      60: istore_3
      61: iload_3
      62: lookupswitch  { // 2
                     0: 88
                     1: 99
               default: 110
          }
      88: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      91: ldc           #7                  // String 1
      93: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      96: goto          110
      99: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
     102: ldc           #9                  // String 2
     104: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     107: goto          110
     110: return
}

ふむふむ。どうやら文字列のhashCodeを使ってswitchで分岐してから、その中でString#equalsで等しいことをチェック。等しければ0からの連番となる固定値を割り振っていく。この値で再度switchする、という仕組みのようです。
Java6以前のままで同様の処理を書いたら、こんな感じになるのかな。

final class Main {

    void f(String s) {
        int a = -1;
        switch (s.hashCode()) {
            case 3104: // "aa"のhashCode
                if (s.equals("aa")) {
                    a = 0;
                }
                break;
            case 3136: // "bb"のhashCode
                if (s.equals("bb")) {
                    a = 1;
                }
                break;
            default:
        }
        switch (a) {
            case 0:
                System.out.println("1");
                break;
            case 1:
                System.out.println("2");
                break;
            default:
        }
    }

}

javapしてみる。

Compiled from "1.java"
final class Main {
  Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void f(java.lang.String);
    Code:
       0: iconst_m1
       1: istore_2
       2: aload_1
       3: invokevirtual #2                  // Method java/lang/String.hashCode:()I
       6: lookupswitch  { // 2
                  3104: 32
                  3136: 46
               default: 60
          }
      32: aload_1
      33: ldc           #3                  // String aa
      35: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      38: ifeq          60
      41: iconst_0
      42: istore_2
      43: goto          60
      46: aload_1
      47: ldc           #5                  // String bb
      49: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      52: ifeq          60
      55: iconst_1
      56: istore_2
      57: goto          60
      60: iload_2
      61: lookupswitch  { // 2
                     0: 88
                     1: 99
               default: 110
          }
      88: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      91: ldc           #7                  // String 1
      93: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      96: goto          110
      99: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
     102: ldc           #9                  // String 2
     104: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     107: goto          110
     110: return
}

だいたい一緒になりました。


最後に、もしhashCodeが同じ文字列を使ったらどうなるかを試します。今度はJava6以前のコードとまとめてやってしまいましょう。

final class Main {

    void f(String s) {
        switch (s) {
            case "an":
                System.out.println("1");
                break;
            case "c0":
                System.out.println("2");
                break;
            default:
        }
    }

    void f2(String s) {
        int a = -1;
        switch (s.hashCode()) {
            case 3117:
                if (s.equals("an")) {
                    a = 0;
                }
                else if (s.equals("c0")) {
                    a = 1;
                }
                break;
            default:
        }
        switch (a) {
            case 0:
                System.out.println("1");
                break;
            case 1:
                System.out.println("2");
                break;
            default:
        }
    }

}
Compiled from "2.java"
final class Main {
  Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void f(java.lang.String);
    Code:
       0: aload_1
       1: astore_2
       2: iconst_m1
       3: istore_3
       4: aload_2
       5: invokevirtual #2                  // Method java/lang/String.hashCode:()I
       8: lookupswitch  { // 1
                  3117: 28
               default: 53
          }
      28: aload_2
      29: ldc           #3                  // String c0
      31: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      34: ifeq          42
      37: iconst_1
      38: istore_3
      39: goto          53
      42: aload_2
      43: ldc           #5                  // String an
      45: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      48: ifeq          53
      51: iconst_0
      52: istore_3
      53: iload_3
      54: lookupswitch  { // 2
                     0: 80
                     1: 91
               default: 102
          }
      80: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      83: ldc           #7                  // String 1
      85: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      88: goto          102
      91: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      94: ldc           #9                  // String 2
      96: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      99: goto          102
     102: return

  void f2(java.lang.String);
    Code:
       0: iconst_m1
       1: istore_2
       2: aload_1
       3: invokevirtual #2                  // Method java/lang/String.hashCode:()I
       6: lookupswitch  { // 1
                  3117: 24
               default: 52
          }
      24: aload_1
      25: ldc           #5                  // String an
      27: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      30: ifeq          38
      33: iconst_0
      34: istore_2
      35: goto          52
      38: aload_1
      39: ldc           #3                  // String c0
      41: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      44: ifeq          52
      47: iconst_1
      48: istore_2
      49: goto          52
      52: iload_2
      53: lookupswitch  { // 2
                     0: 80
                     1: 91
               default: 102
          }
      80: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      83: ldc           #7                  // String 1
      85: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      88: goto          102
      91: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      94: ldc           #9                  // String 2
      96: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      99: goto          102
     102: return
}

やはり、caseが1つしかできませんでした。