2011/09/13

[java]引数にfinalをつけるとどうなるのだろう (1)

C++でアドレスを渡す場合、constをつけている。
同じ感覚でJavaでもfinalを書いていたのだが、AndroidのAPIではつけているものを見かけない。
ネットで調べると、侃々諤々というか、不毛というか、よくわからんごとなってきた。

ちょっとだけやってみよう。

Oracleのサーバ向けのドキュメントでは、チューニングガイドにちょっと載っていた。
http://download.oracle.com/docs/cd/E19528-01/820-1613/abebm/index.html


   1: package com.blogpost.hiro99ma;
   2:  
   3: public class FinalTest {
   4:     static void print(byte[] b) {
   5:         for(int i=0; i<b.length; i++) {
   6:             System.out.print(b[i]);
   7:         }
   8:         System.out.println();
   9:         
  10:         b[0] = 5;
  11:     }
  12:     
  13:     public static void main(String[] args) {
  14:         byte[] a = new byte[] { 1, 2, 3 };
  15:         print(a);
  16:  
  17:         for(int i=0; i<a.length; i++) {
  18:             System.out.print(a[i]);
  19:         }
  20:         System.out.println();
  21:     }
  22: }

まあ、シンプルに。
実行すると、こうなる。

123
523

予想通りだ。
classファイルは846byte。

バイトコードは、こうなってる。

// Compiled from FinalTest.java (version 1.6 : 50.0, super bit)
public class com.blogpost.hiro99ma.FinalTest {
  
  // Method descriptor #6 ()V
  // Stack: 1, Locals: 1
  public FinalTest();
    0  aload_0 [this]
    1  invokespecial java.lang.Object() [8]
    4  return
      Line numbers:
        [pc: 0, line: 3]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: com.blogpost.hiro99ma.FinalTest
  
  // Method descriptor #15 ([B)V
  // Stack: 3, Locals: 2
  static void print(byte[] b);
     0  iconst_0
     1  istore_1 [i]
     2  goto 17
     5  getstatic java.lang.System.out : java.io.PrintStream [16]
     8  aload_0 [b]
     9  iload_1 [i]
    10  baload
    11  invokevirtual java.io.PrintStream.print(int) : void [22]
    14  iinc 1 1 [i]
    17  iload_1 [i]
    18  aload_0 [b]
    19  arraylength
    20  if_icmplt 5
    23  getstatic java.lang.System.out : java.io.PrintStream [16]
    26  invokevirtual java.io.PrintStream.println() : void [27]
    29  aload_0 [b]
    30  iconst_0
    31  iconst_5
    32  bastore
    33  return
      Line numbers:
        [pc: 0, line: 5]
        [pc: 5, line: 6]
        [pc: 14, line: 5]
        [pc: 23, line: 8]
        [pc: 29, line: 10]
        [pc: 33, line: 11]
      Local variable table:
        [pc: 0, pc: 34] local: b index: 0 type: byte[]
        [pc: 2, pc: 23] local: i index: 1 type: int
      Stack map table: number of frames 2
        [pc: 5, append: {int}]
        [pc: 17, same]
  
  // Method descriptor #36 ([Ljava/lang/String;)V
  // Stack: 4, Locals: 3
  public static void main(java.lang.String[] args);
     0  iconst_3
     1  newarray byte [8]
     3  dup
     4  iconst_0
     5  iconst_1
     6  bastore
     7  dup
     8  iconst_1
     9  iconst_2
    10  bastore
    11  dup
    12  iconst_2
    13  iconst_3
    14  bastore
    15  astore_1 [a]
    16  aload_1 [a]
    17  invokestatic com.blogpost.hiro99ma.FinalTest.print(byte[]) : void [37]
    20  iconst_0
    21  istore_2 [i]
    22  goto 37
    25  getstatic java.lang.System.out : java.io.PrintStream [16]
    28  aload_1 [a]
    29  iload_2 [i]
    30  baload
    31  invokevirtual java.io.PrintStream.print(int) : void [22]
    34  iinc 2 1 [i]
    37  iload_2 [i]
    38  aload_1 [a]
    39  arraylength
    40  if_icmplt 25
    43  getstatic java.lang.System.out : java.io.PrintStream [16]
    46  invokevirtual java.io.PrintStream.println() : void [27]
    49  return
      Line numbers:
        [pc: 0, line: 14]
        [pc: 16, line: 15]
        [pc: 20, line: 17]
        [pc: 25, line: 18]
        [pc: 34, line: 17]
        [pc: 43, line: 20]
        [pc: 49, line: 21]
      Local variable table:
        [pc: 0, pc: 50] local: args index: 0 type: java.lang.String[]
        [pc: 16, pc: 50] local: a index: 1 type: byte[]
        [pc: 22, pc: 43] local: i index: 2 type: int
      Stack map table: number of frames 2
        [pc: 25, append: {byte[], int}]
        [pc: 37, same]
}

お、と思ったかもしれないが、バイトコードは読んだことはない。
まあ、アセンブラも適当に読めるものだから、なんとかなるだろう。

Method #6は、デフォルトコンストラクタだろう。
Method #15は、print()。aloadはarrayでiloadはintか?
「b[0]=5」は29~31行でやっているようだ。
「b[2]=9」にすると、ちょっと変わった。

29  aload_0 [b]
30  iconst_2
31  bipush 9

うーん、iconstからbipushになったな・・・。
えーい、これならどうだ!

b[0] = 0;
b[0] = 1;
b[0] = 2;
b[0] = 3;
b[0] = 4;
b[0] = 5;
b[0] = 6;

結果は・・・

29  aload_0 [b]
30  iconst_0
31  iconst_0
32  bastore
33  aload_0 [b]
34  iconst_0
35  iconst_1
36  bastore
37  aload_0 [b]
38  iconst_0
39  iconst_2
40  bastore
41  aload_0 [b]
42  iconst_0
43  iconst_3
44  bastore
45  aload_0 [b]
46  iconst_0
47  iconst_4
48  bastore
49  aload_0 [b]
50  iconst_0
51  iconst_5
52  bastore
53  aload_0 [b]
54  iconst_0
55  bipush 6
57  bastore

2つわかった。
iconstとipushが切り替わる境目は、代入する値が5以下か6以上かであること。
もう1つは、bastoreは毎回呼ばれるということ。

bはbyteのbだろう。intにして確認。

29  aload_0 [b]
30  iconst_0
31  iconst_0
32  iastore
33  aload_0 [b]
34  iconst_0
35  iconst_1
36  iastore
37  aload_0 [b]
38  iconst_0
39  iconst_2
40  iastore
41  aload_0 [b]
42  iconst_0
43  iconst_3
44  iastore
45  aload_0 [b]
46  iconst_0
47  iconst_4
48  iastore
49  aload_0 [b]
50  iconst_0
51  iconst_5
52  iastore
53  aload_0 [b]
54  iconst_0
55  bipush 6
57  iastore

「aload_0 [b]」で、引数の先頭アドレスを取ってきて、その次の「iconst_x」でオフセットを設定。
その次の「iconst_y」で代入する値を決めて、その次の「iastore」で代入、というところか。

違ってたら済まん。

驚いたのは、b[0]に代入するだけのコードを立て続けに書いたのに、最適化してないこと。
まるでvolatile変数みたいだ。
デフォルトだとこうなるのかしら。

 

最初に戻って。
これの引数にfinalをつけてみよう。

   1: package com.blogpost.hiro99ma;
   2:  
   3: public class FinalTest {
   4:     static void print(final byte[] b) {
   5:         for(int i=0; i<b.length; i++) {
   6:             System.out.print(b[i]);
   7:         }
   8:         System.out.println();
   9:         
  10:         b[0] = 5;
  11:     }
  12:     
  13:     public static void main(String[] args) {
  14:         byte[] a = new byte[] { 1, 2, 3 };
  15:         print(a);
  16:  
  17:         for(int i=0; i<a.length; i++) {
  18:             System.out.print(a[i]);
  19:         }
  20:         System.out.println();
  21:     }
  22: }

実行結果は同じ。

123
523

finalでは指している先の変更は妨げることができないというのは、わかった。
では、バイトコードに違いはあるのだろうか。

// Compiled from FinalTest.java (version 1.6 : 50.0, super bit)
public class com.blogpost.hiro99ma.FinalTest {
  
  // Method descriptor #6 ()V
  // Stack: 1, Locals: 1
  public FinalTest();
    0  aload_0 [this]
    1  invokespecial java.lang.Object() [8]
    4  return
      Line numbers:
        [pc: 0, line: 3]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: com.blogpost.hiro99ma.FinalTest
  
  // Method descriptor #15 ([B)V
  // Stack: 3, Locals: 2
  static void print(byte[] b);
     0  iconst_0
     1  istore_1 [i]
     2  goto 17
     5  getstatic java.lang.System.out : java.io.PrintStream [16]
     8  aload_0 [b]
     9  iload_1 [i]
    10  baload
    11  invokevirtual java.io.PrintStream.print(int) : void [22]
    14  iinc 1 1 [i]
    17  iload_1 [i]
    18  aload_0 [b]
    19  arraylength
    20  if_icmplt 5
    23  getstatic java.lang.System.out : java.io.PrintStream [16]
    26  invokevirtual java.io.PrintStream.println() : void [27]
    29  aload_0 [b]
    30  iconst_0
    31  iconst_5
    32  bastore
    33  return
      Line numbers:
        [pc: 0, line: 5]
        [pc: 5, line: 6]
        [pc: 14, line: 5]
        [pc: 23, line: 8]
        [pc: 29, line: 10]
        [pc: 33, line: 11]
      Local variable table:
        [pc: 0, pc: 34] local: b index: 0 type: byte[]
        [pc: 2, pc: 23] local: i index: 1 type: int
      Stack map table: number of frames 2
        [pc: 5, append: {int}]
        [pc: 17, same]
  
  // Method descriptor #36 ([Ljava/lang/String;)V
  // Stack: 4, Locals: 3
  public static void main(java.lang.String[] args);
     0  iconst_3
     1  newarray byte [8]
     3  dup
     4  iconst_0
     5  iconst_1
     6  bastore
     7  dup
     8  iconst_1
     9  iconst_2
    10  bastore
    11  dup
    12  iconst_2
    13  iconst_3
    14  bastore
    15  astore_1 [a]
    16  aload_1 [a]
    17  invokestatic com.blogpost.hiro99ma.FinalTest.print(byte[]) : void [37]
    20  iconst_0
    21  istore_2 [i]
    22  goto 37
    25  getstatic java.lang.System.out : java.io.PrintStream [16]
    28  aload_1 [a]
    29  iload_2 [i]
    30  baload
    31  invokevirtual java.io.PrintStream.print(int) : void [22]
    34  iinc 2 1 [i]
    37  iload_2 [i]
    38  aload_1 [a]
    39  arraylength
    40  if_icmplt 25
    43  getstatic java.lang.System.out : java.io.PrintStream [16]
    46  invokevirtual java.io.PrintStream.println() : void [27]
    49  return
      Line numbers:
        [pc: 0, line: 14]
        [pc: 16, line: 15]
        [pc: 20, line: 17]
        [pc: 25, line: 18]
        [pc: 34, line: 17]
        [pc: 43, line: 20]
        [pc: 49, line: 21]
      Local variable table:
        [pc: 0, pc: 50] local: args index: 0 type: java.lang.String[]
        [pc: 16, pc: 50] local: a index: 1 type: byte[]
        [pc: 22, pc: 43] local: i index: 2 type: int
      Stack map table: number of frames 2
        [pc: 25, append: {byte[], int}]
        [pc: 37, same]
}

まったく同じだ。
そもそも「static void print(int[] b);」なので、finalがなくなっている。

 

配列だからか?
プリミティブ型の配列だからなのか??

というところで、疲れ果てたので寝る。

2 件のコメント:

  1. Javaのvoid print(final byte[] b)って、CやC++だとvoid print(unsigned char* const b)になりますよね(ポインタの指す先は変更できるが、変数そのものへの代入が出来ないという意味で同じ)。こっちのconstも出力されるアセンブリはconstのない場合と大差ないものになると思います。

    返信削除
  2. ああ、なるほど!
    言われてみればそうですね。。
    MMUみたいなのでやらない限り、コンパイラの役割ですね。
    ありがとうございます。

    返信削除

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。