Inner Classのstatic有りと無しの違い
昨日、結婚式の招待状が届きました。
古い知人のモノで、当然行く予定。
日取りは10/19だそうで。
…って10/19って、情報処理技術者試験の平成20年度秋期試験期日じゃないか。うう。
ということで、あきらめて…悔しいので10月にSJC-Pあたりをとろうと考えてみました。
大体、いまだに取ってないのもアレだし。
そうすると、やはり細かいところが気になってきたわけで…。
class内classの種類
インナークラスはメソッド内に名有りで書いたり、名無しでインスタンス作成のために書いたりというのもあるけど、ここでは差が分かりにくいstaticなクラス内クラスとstaticではないクラス内クラスを比べます。
staticなクラス内クラスとは、
class A { static class X { } }
のXのように、staticで修飾されているクラス。
逆にstaticでないクラス内クラスとは、
class A { class Y { } }
のYのように、static修飾されてないクラスの事です。
こうすると、クラス.内部クラス というようにアクセスできるわけですが、面白いことにYのクラスでも同じようにアクセスが可能です。
C:\Develop>copy con A.java class A { static class X { } class Y { } void method() { A.X x = new A.X(); A.Y y = new A.Y(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe -g:none A.java C:\Develop>dir ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は 3574-3602 です C:\Develop のディレクトリ 2008/09/20 00:52 <DIR> . 2008/09/20 00:52 <DIR> .. 2008/09/20 00:52 160 A$X.class 2008/09/20 00:52 207 A$Y.class 2008/09/20 00:52 258 A.class 2008/09/20 00:52 129 A.java 2008/08/27 15:35 <DIR> IDE 2008/09/20 00:11 <DIR> Language 2008/09/17 07:13 <DIR> Server 4 個のファイル 754 バイト 5 個のディレクトリ 160,092,176,384 バイトの空き領域 C:\Develop>
とまぁ、こうしてみると普通に成功します。
ちなみにコンパイルのオプションには、今回の解析で使いやすいようにデバッグ情報は削除しておきました。
そこで中身
クラス内クラスは、「親クラス$子クラス.class」というようなファイル名で出力されます。
見てのとおり、A$X.classとA$Y.classではファイルサイズが違うことから、中身も違うのは想像できるでしょう。
じゃぁ、javapで中を覗いてみるとします。
C:\Develop>Language\jdk1.6.0_07\bin\javap.exe -verbose A$X class A$X extends java.lang.Object InnerClass: #10= #2 of #8; //X=class A$X of class A minor version: 0 major version: 50 Constant pool: const #1 = Method #3.#7; // java/lang/Object."<init>":()V const #2 = class #9; // A$X const #3 = class #12; // java/lang/Object const #4 = Asciz <init>; const #5 = Asciz ()V; const #6 = Asciz Code; const #7 = NameAndType #4:#5;// "<init>":()V const #8 = class #13; // A const #9 = Asciz A$X; const #10 = Asciz X; const #11 = Asciz InnerClasses; const #12 = Asciz java/lang/Object; const #13 = Asciz A; { A$X(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return } C:\Develop>Language\jdk1.6.0_07\bin\javap.exe -verbose A$Y class A$Y extends java.lang.Object InnerClass: #14= #3 of #12; //Y=class A$Y of class A minor version: 0 major version: 50 Constant pool: const #1 = Field #3.#10; // A$Y.this$0:LA; const #2 = Method #4.#11; // java/lang/Object."<init>":()V const #3 = class #13; // A$Y const #4 = class #16; // java/lang/Object const #5 = Asciz this$0; const #6 = Asciz LA;; const #7 = Asciz <init>; const #8 = Asciz (LA;)V; const #9 = Asciz Code; const #10 = NameAndType #5:#6;// this$0:LA; const #11 = NameAndType #7:#17;// "<init>":()V const #12 = class #18; // A const #13 = Asciz A$Y; const #14 = Asciz Y; const #15 = Asciz InnerClasses; const #16 = Asciz java/lang/Object; const #17 = Asciz ()V; const #18 = Asciz A; { final A this$0; A$Y(A); Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: aload_1 2: putfield #1; //Field this$0:LA; 5: aload_0 6: invokespecial #2; //Method java/lang/Object."<init>":()V 9: return } C:\Develop>
差異を見てみると、static修飾をされていないとthis$0の定義が走り、字面上は存在しない形でデフォルトコンストラクタの引数が一つ増えている。
enclosingしてみる
ということは、enclosingアクセスできるかどうかが違うということだろうか。
C:\Develop>copy con B.java class B { private int i = 10; class Y { private int x = B.this.i; } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe B.java C:\Develop>copy con C.java class C { private int i = 11; static class X { private int x = C.this.i; } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe C.java C.java:4: static でない 変数 this を static コンテキストから参照することはできま せん。 private int x = C.this.i; ^ エラー 1 個 C:\Develop>
なるほど。enclosingアクセスができるように初期化するコストが、static修飾のないクラス内クラスには必要になるということですね。
スコープが違う?
さて、そうなるとこのクラス内クラス、スコープが気になります。
C:\Develop>copy con D.java class D { class Y { } static void method () { F.Y y = new F.Y(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe D.java D.java:5: シンボルを見つけられません。 シンボル: クラス Y 場所 : F の クラス F.Y y = new F.Y(); ^ D.java:5: シンボルを見つけられません。 シンボル: クラス Y 場所 : F の クラス F.Y y = new F.Y(); ^ エラー 2 個 C:\Develop>copy con E.java class E { class Y { } } class ESub { void method() { E.Y y = new E.Y(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe E.java E.java:7: E.Y を含む囲うインスタンスが必要です。 E.Y y = new E.Y(); ^ エラー 1 個 C:\Develop>copy con F.java class F { static class X { } static void method() { F.X x = new F.X(); } } class FSub { void method () { F.X x = new F.X(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe F.java C:\Develop>
static修飾されないと、staticでないとアクセスできない場所からはアクセスできないらしい。
そりゃそうですよね。
つまるところ
つまり、static修飾されていないと、
- enclosingアクセスできるが、そのために初期化コストがかかる
- クラスの外部からのインスタンス作成を標準で抑制できる
というところが違うってことですか。
後者は私が文法を知らないだけの可能性があって、ちょいと微妙ですが…。
まぁ、リフレクション使えばイケそうな気がします。