パスワードを忘れた? アカウント作成
432569 journal

quabbinの日記: [Java] Inner Classのstatic有りと無しの違い

日記 by quabbin

昨日、結婚式の招待状が届きました。
古い知人のモノで、当然行く予定。
日取りは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修飾されていないと、
1. enclosingアクセスできるが、そのために初期化コストがかかる
2. クラスの外部からのインスタンス作成を標準で抑制できる
というところが違うってことですか。
後者は私が文法を知らないだけの可能性があって、ちょいと微妙ですが…。
まぁ、リフレクション使えばイケそうな気がします。

でもSJC-Pって
でもSJC-Pって、こんなところまでは出ませんよね…。
なんかしょうもない事をしている気がする

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

身近な人の偉大さは半減する -- あるアレゲ人

読み込み中...