Deep Side of Java〜Java 言語再入門 第2回 〜 Java 文法を中心に

オブジェクト指向に関する仕様

クラス変数とクラスメソッド






オブジェクト指向に関する仕様

クラス変数とクラスメソッド

ここで「クラス変数」と「クラスメソッド」について説明しよう。

クラス変数

通常のインスタンス変数は、インスタンスに所属するために、インスタンスにとって固有である。つまり、インスタンスによって値が異なるのが当然である。しかし、インスタンスには無関係に、すべてのインスタンスによって共有したい変数もありうる。これを「クラス変数」として、クラスに所属するものとして定義する。だから逆に言えば、この「クラス変数」は、インスタンスを生成せずに使うことができる。たとえば、

        System.out.println( "test" );

で、標準出力を表す out は、java.lang.System クラスに属する PrintStream 型のクラス変数である。だから、System.out はインタプリタに組み込まれたクラスである java.lang.System に属するので、そのクラス変数である out は一切のインスタンス生成なしに利用できる。

また、定数を表すのに、次の表現が良く使われる。Java ではプリプロセッサが存在しないので、#define 定数のように即値を回避する手段として、このクラス変数を使う。

public static final double PI = 3.1415.....;
double x = Math.PI * 2;

これは java.lang.Math クラス(数学関数関連)で定義されているπの値である。つまり、define マクロで定数を定義するのではなく、このように static final で修飾すると(つまり、static == クラス変数 で final == 上書き不可 で宣言されると)、コンパイラはこれは「定数である」と判定し、変数参照のオーバーヘッドなしにアクセスできるように定数としてコンパイルするような最適化が働くのである。だから実質上 public static final で宣言された変数は、どこでも使える定数である。

上記の例で見たように、クラス変数を参照するには 「クラス名.変数名」で参照できる。

メソッドの宣言

では、一般的にメソッドの宣言はどうするのだろう。

修飾子 戻り値の型 メソッド名( 仮引数の並び ) [ throws 投げる例外 ] (本体のブロック|;)

である(「例外」については後でまとめて述べる)。修飾子は次のものを順番は無関係に取れる。

public, protected, private
これらは通例の通り。しかし、メソッドは上書きされるので、基底クラスで宣言されたアクセス修飾子より「狭く」することはできるが、「広く」することは出来ない。

abstract
抽象メソッドを定義する。この場合のみ本体はブロックではなくて、「;」空文である。

static
クラスメソッドとして宣言する。

final
メソッドの継承による上書きを禁止する。これも最適化を促進するが、継承を利用できなくなるので設計上の注意が必要である。

synchronized
Java はマルチスレッドなので、いわゆる mutex(相互排他ロック) が指定できないといけない。文のブロックを mutex を実行する synchronized 文もあるが、メソッド単位で mutex できる。mutex は並行で動作するスレッドの下で、そのメソッドや文が、「割り込まれずに」最後まで実行されることを保証する機構である。アトミックに実行されるべきメソッドや文を synchronized メソッド(文)によって囲うことでスレッドセーフにできる。「スレッドの使い方」を参照のこと。

native
システムコールを呼び出す、システム依存のクラスライブラリを実装するのに使う。そのメソッド本体が Java ではなく、C言語などで作成されたターゲットマシンの機械語プログラムであることを示す。

クラスメソッド

また、メソッドでもインスタンス化に無関係なメソッドは、クラスメソッドにできる。

ここでは、クラスメソッドについて解説する。クラス変数と同様に、クラスメソッドはクラスに属し、インスタンスの生成とは無関係に利用できる。ということは、クラスメソッドは一切のインスタンス変数へのアクセスがなされないメソッドでなくてはならない。だから、クラスメソッドはある操作をまとめた共用メソッドなどとして使われることが多い。数学関数や、型変換関数はその実例である。

... in java.lang.Math
static double sin( double v );

... in java.lang.Integer
static int parseInt( String s );

.........
        int iv = Integer.parseInt( "132" );
        Sytem.out.println( Math.sin( Math.PI * (double)iv ) ); 

クラスメソッドは、クラスに固有であるために、サブクラスで上書きされた場合にも継承されるではなくて、基底クラスのクラスメソッドを「隠蔽」するだけである。

クラスのロードと初期化

また、初期化子はCと違ってコンパイル時に静的に決定されている必要はない。要するに、「リテラル値」でなくてはならないという制限はない。だから、メソッド呼び出しや変数評価式なども自由に書ける。ただし、クラス変数の初期化子には、リテラルの他にはクラス変数かクラスメソッド呼び出ししか使えない。ここらへんの事情は、変数初期化がどうさなれるのかを理解するのが速い。Java インタプリタは必要に応じて動的にクラスをロードしているため、次の手順で初期化をする。

  1. 初めてそのクラスのインスタンスが生成されるか、クラス変数やクラスメソッドへのアクセスがあった時に、当該クラスが動的にロードされる。

  2. まず、そのクラスの親クラスがロードされていなければ、親クラスのロードと初期化を再帰的に実行する。

  3. クラス変数とクラスメソッドを初期化する。あるいは static {ブロック} による初期化ブロックがあればそれを実行する。これらの実行は単純に上から下へと実行されて行く。 インスタンス生成がない場合には、ここで終るか、アプリケーションのトップレベルであれば、当該クラスの public static void main() 関数を呼び出す。だから、クラスメソッド内では、インスタンス変数/インスタンスメソッドの利用はできないのである。この時、もし未ロードのクラスを使う場合には、それらのクラスがやはり再帰的にロードされる。

  4. もし、インスタンス生成があれば、メモリを確保してコンストラクタに渡す。



copyright by K.Sugiura, 1996-2006