戻る
本章で取り上げるテーマは、staticで修飾されたフィールドやメソッドについてである。
これらのフィールドやメソッドは、通常のフィールドやメソッドとは異なる動作をする。
なぜこのようなメンバー(フィールドやメソッド)が必要かを示すために問題を提示する。
いま、次のような機能を持つクラスAを作成したいとする。
・ クラスAは、how_manyというメンバーを持つ
・ メンバー関数how_manyは、現在、自分のクラスのインスタンスがいくつ存在するかを出力する。
例えば、次のようにクラスAのインスタンスを3つ作ったとする。
A a1 = new A( );
A a2 = new A( );
A a3 = new A( );
この場合、a1.how_many( )を呼び出すと、インスタンス数の3が報告される。(a2.how_many(
)でもa3.how_many( )でもよい。)
a1.how_many( ); ・・・・・3が出力される
このようなクラスAを作りなさいというのが、この問題の意味である。
考察(1)―フィールド数で数える
この問題を解くポイントは、オブジェクトの数を数える為の変数(その変数名をcountとする)をどのように用意するかにある。
すぐ思いつくのが、countをクラスAのフィールドにすることである。
class A {
private int count = 0; ・・・・・インスタンス数を数える
…
};
クラスAのインスタンスが作られる時、コンストラクタが実行される。そこで、コンストラクタの中でcountをカウントアップする。
A( ) { ・・・・・クラスAのコンストラクタ
++count ・・・・・countの値を1つ大きくする
}
こうすれば、クラスAのインスタンスが作られる度にcountがカウントアップされていく。(ように見える)
しかし、残念ながらこの方法には問題がある。
というのは、フィールドはインスタンスごとに作られるからである。フィールドは、インスタンスごとに別の値を持つ。
A a1 - new A( );
A a2 = new A( );
A a3 = new A( );
例えば、このように3つのインスタンスを作った時、a.countもb.countもc.countも全て別の変数である。
それぞれのコンストラクタでこれらの値をカウントアップしても、これらの変数は1にしかならない。
つまり、この問題を解くには、countをフィールドにしてはいけないという事である。では、どうしたらよいだろう。
考察(2)―利用クラスで考える
インスタンス数を数える変数countをクラスAとは別のクラスに置いたらどうだろう。すなわちクラスAを利用する側のクラスにcountを置くのである。
public class B { ・・・・このクラスでクラスAを使う
public static void main(String[ ]
args) {
int count = 0; ・・・・ここでインスタンス数を数える
A a1 = new A( );
count++; ・・・・Aのインスタンスを作る度にカウントアップする
A a2 = new A( );
count++;
A a3 = new A( );
count++;
a1.how_many( ); ・・・・ここでインスタンス数を出力
}
}
しかし、このやり方はナンセンスである。最後のところでhow_manyを呼んでインスタンス数を出力しようとしている。
しかし、クラスAからは変数countが見えないのだから、インスタンス数を報告しようにも不可能である。
それどころか、あえてhow_manyを呼ばなくてもcountを見ればインスタンス数は分かる。
つまり、クラスAのインスタンス数を数える問題は、クラスBの中に閉じている。しかし、これでは、この問題の意味に反する。
この問題は、あくまでもクラスA自身に自分のインスタンス数を数えさせる事に意義がある。
インスタンスの数を数える為の変数については、上記のような問題がある。
さて、どのようにしたら上手くインスタンスの数を数える事ができるか―ヒントは、本章で説明するstaticで修飾されたフィールドを使う事である。
クラス変数とインスタンス変数
本章では、staticで修飾されたフィールドと一般的なフィールドの違いについて説明する。
staticで修飾されたフィールドの特色は、次の2つである。
・ クラスに所属し、複数のインスタンスを作っても変数が1つしか作られない。
・ インスタンスを生成せずに使う事が出来る。(参照はクラス名で行う)
staticで修飾されたフィールド
前項の最後に示したstaticで修飾されたフィールドというのは、staticをつけて宣言されたフィールドの事である。
class A {
int each; ・・・・これは普通のフィールド
static int common; ・・・・staticで修飾されたフィールド
…
…
};
staticで修飾されたフィールド(上の例でいうとcommon)は、普通のフィールドとは異なり、
複数のインスタンスを作っても変数が1つしか作られない。
したがって、例えばAのインスタンスとしてa1とa2を作ったとすると、a1.commonとa2.commonは同じ値になる。
しかし、普通のフィールド(上の例でいうとeach)は、インスタンスごとに別の変数となるので、a1.eachとa2.eachは異なる変数になる。
staticで修飾されたフィールドをテストする為のサンプル
サンプル:Static.java
//
//Static.java---staticで修飾されたフィールド
//
class A {
int each;
static int common;
A(int n) {
each = n;
}
void show( ) {
System.out.println("each =
" + each);
System.out.println("common
= " + common);
}
}
public class Static {
public static void main(String[ ]
args) {
A a1 = new A(100);
A a2 = new A(200) ;
a1.common = 9999;
a1.show( );
a2.show( );
}
}
このアプリケーションに出てくるクラスAは、2つのフィールドを持っている。
int each; ・・・・・・・・・・・普通のフィールド
static int common; ・・・staticで修飾されたフィールド
クラスAのコンストラクタは、これら2つのフィールドのうち、普通のフィールドであるeachだけを初期化している。
commonについては何もしていない。
A(int n) {
each = n; ・・・・与えられた引数でeachを初期化する
}
一方、メソッドではクラスAのインスタンスを2つ生成している。
A a1 = new A(100);
A a2 = new A(200);
この結果、それぞれのeachフィールドは次のように初期化される。
a1.each=100;
a2.each=200;
しかし、staticで修飾されたフィールドcommonについては、まだ初期化されていない。そこで、次のようにa1のcommonについて初期化している。
a1.common = 9999;
a2.commonについては何もしない。
しかし、commonはstaticで修飾されたフィールドなので、a1.commonとa2.commonは同じ値9999になるはずである。
それぞれのshowメソッドを呼んで確かめる。
a1.show( );
a2.show( );
showメソッドは、2つのフィールドの値を出力するものである。
はたしてa2.commonがa1.commonと同じ値9999になっているか、次の実行例で確認してみる。
実行例
$ java Static
each = 100 ・・・・・・・・・・a1.each
common = 9999 ・・・・・・a1.common
each = 200 ・・・・・・・・・・a2.each
common = 9999 ・・・・・・a2.common
$ _
クラス変数とインスタンス変数
staticで修飾されたフィールドは、クラスに所属し、そのクラスの全てのインスタンスによって共存される。
今、commonをクラスAのstaticで修飾されたフィールドとし、a1、a2、a3をAのインスタンスとする。
すると、次はいずれもcommonという同じ変数を参照する事になる。
a1.common
a2.common
a3.common
このようにstaticで修飾されたフィールドは、どのインスタンスで修飾しても同じである。
a1.common ←インスタンス名で修飾
しかし、a1.commonのようにインスタンスで修飾すると、あたかもcommonがa1に所属しているかのように見えてしまう。
そこで、staticで修飾されたフィールドは、(インスタンス名で修飾する代わりに)クラス名で修飾することが出来る。
A.common ←クラス名で修飾
むしろこの書き方のほうが、クラスに所属する変数という意味を強調できるので一般的である。
a1.each ・・・・一般的なフィールドはインスタンス名で修飾
A.common ・・staticフィールドはクラス名で修飾
このようにstaticで修飾さらたフィールドは、クラス名で修飾されるので、クラス変数と呼ばれる。
それに対して、一般的なフィールドはインスタンス名で修飾されるので、インスタンス変数と呼ばれる。
クラス変数は、クラス名で修飾する事もインスタンス名で修飾する事も出来る。
しかし、クラス名でしか修飾できない場合もある。それを以下で説明する。
擬似グローバル変数と擬似グローバル定数
C言語には、グローバル変数という変数が存在する。
グローバル変数は、全ての関数(関数はjavaのメソッドに相当する)から参照する事が出来る。
javaにはグローバル変数の概念が無い。もちろんフィールドを使えば、同じクラスに所属する全てのメソッドから参照する事は出来る。
しかし、他のクラスから参照できるとは限らない。
また、そのフィールドを参照するためには、そのクラスのインスタンスを生成しなければならない。
クラス変数を使えば、C言語のグローバル変数に近い事を実現する事が出来る。
というのは、クラス変数は、インスタンスを生成せずに参照する事が出来るからである。
例を示す。いまeventという名前をグローバル変数のごとく(擬似グローバル変数と呼ぶ)使いたいとする。
それには、適当なクラスを作成し、そこにeventという名前のフィールドを置く。
class Ace { ・・・・・適当なクラス
int event; ・・・・・フィールド
}
フィールドeventを擬似グローバル変数にするには、eventをクラス変数にする必要がある。すなわちstaticで修飾する。
class Ace {
static int event; ・・・・クラス変数にする
}
さらに、他のクラスから参照できるようにするためには、アクセス修飾子のpublicをつける。
class Ace {
public static int event; ・・・・publicをつける
}
これで準備は済んだ。もしeventがクラス変数でなかったとしたら、クラスAceのインスタンスを生成してからでないと、eventを使う事は出来ない。
Ace A = new Ace( ); ・・インスタンスを生成してから
a,event = 1; ・・・・・・・・・eventを使う
しかし、eventはクラス変数なので、インスタンスを生成する必要は無いので、いきなり使う事が出来る。
その場合は(インスタンスが存在しないので)インスタンス名で修飾することはできない。
しかし、クラス名で修飾すればよいのである。
Ace.event = 1; ・・・・・インスタンスを生成せずに使える。
これなら(クラス名で修飾しなければならないという制限はあるものの)eventをあたかもC言語のグローバル変数のように使う事が出来る。
同様にして擬似グローバル定数を使う事もできる。クラス変数にfinalをつけてしまえばよいのである。
例えば、クラスAceにMagicという擬似グローバル定数を追加したければ、次のようにする。
class Ace {
public static int event;
public static final int Magic = 53; ・・・擬似グローバル定数
}
擬似グローバル定数は、値を変更できない。(値を1回しか代入できない)点を除いて、擬似グローバル変数と同じである。
サンプル:Global.java
//
//Global.java---擬似グローバル変数
//
class Ace {
public static int event; //擬似グローバル変数
public static final int Magic - 53; //擬似グローバル定数
}
public class Global {
public static void main(String[ ]
args) {
Ace.event = 13;
System.out.println("Ace.event
= " + Ace.event);
System.out.println("Ace.Magic
= " + Ace.Magic);
}
}
このmainメソッドに注目する。まず、いきなり擬似グローバル変数のeventに値を代入している事に注意。
Ace.event = 13;
この時点ではまだAceのインスタンスは生成されていない。
しかし、eventはクラス変数なので参照可能である。この後、eventとMagicの値を出力している。
System.out.println("Ace.event
= " + Ace.event);
System.out.println("Ace.Magic
= " + Ace.Magic);
擬似グローバル定数Magicもインスタンスを生成することなく参照できる。
この出力により、その初期値である53が表示されるはずである。次にその実行例を示す。
$ java Global
Ace.event = 13
Ace.Magic = 53
$ _
最初のインスタンス数を数える問題に戻る。
この問題は、staticで修飾されたフィールド―すなわちクラス変数を使う事によりとく事が出来る。
その問題のクラスをAとする。Aは自身のインスタンス数を数える為のクラス変数countを持つ。
class A {
static int count; ・・・・・このクラスのインスタンス数
…
…
}
クラスAのインスタンスが生成されると、コンストラクタが起動される。そこで、コンストラクタの中でcountをカウントアップする。
class A {
static int count ;
A( ) { ・・・・コンストラクタ
++count; ・・・・ここでカウントアップ
}
…
…
}
クラスAは、how_manyというメソッドを持つ。how_manyhは現在、自分のクラスのインスタンスがいくつ存在するかを出力する。
class A {
…
…
void how_many( ) {
System.out.println("クラスAのインスタンス数="
+ count);
}
}
以上をテストする為のサンプル
サンプル:HowMany.java
//
//HowMany.java---生成されたインスタンスの数を数える
//
class A {
static int count; //このクラスのインスタンス数
A( ) {
++count;
//インスタンスが生成されたのでカウントアップ
}
void how_many( );
System.out.println("クラスAのインスタンス数="
+ count);
}
}
public class HowMany {
public static void main(String[ ]
args) {
A a1 = new A( );
A a2 = new A( );
A a3 = new A( );
a1.how_many( );
}
}
実行例
$ java HowMany
クラスAのインスタンス数=3
$ _
初期化ブロック
本節では、クラスが最初にロードされる時に実行される初期化ブロックについて説明する。初期化ブロックは次のような特色を持つ。
・ 1回だけ実行される(複数のインスタンスを生成しても実行されるのは1回だけ)
・ 複数の文を置く事が出来る。
クラス変数の初期化
クラス変数を初期化する最も簡単な方法は、宣言時に初期値を与えればよい。
class Ace {
public static int event = 12; ・・・・初期値を与える
}
しかし、配列の初期化などのように1つの代入では済まないような複雑な初期化を必要とする場合は、初期化ブロックを使う。
初期化ブロックは、メソッドに似ているが、型も名前も引数も持たない。代わりにブロックの前にstaticをつける。
class Walk {
static { ・・・・・・・初期化ブロック/複数の文を置く事が出来る。
…
…
}
}
実行のタイミング
初期化ブロックは、(コンストラクタと同じように)自動的に実行される。
初期化ブロックが実行されるのは、そのクラスが最初にロードされた時である。
具体的には、次のような時である。
・ 最初のインスタンスを生成した時(インスタンスをいくつ生成しても実行されるのは1つだけ)
・ そのクラスを指定してアプリケーションを実行した時(この場合は、インスタンスを生成することなく実行される)
複数のインスタンスを生成しても、初期化ブロックが実行されるのは1回だけである。
最初のインスタンスが生成される時に実行される。そのタイミングは、コンストラクタの実行よりも先である。
初期化ブロック
↓
コンストラクタ
サンプル:Init.java
//
//Init.java---初期化ブロック
//
class Walk {
int time;
public static int run = 12;
Walk( ) {
System.out.println("コンストラクタを実行中");
System.out.println("現在のrun="
+ run);
run = 500;
System.out.println("run=500に変更");
System.out.println("現在のrun="
+ run);
System.out.println("コンストラクタ終了");
System.out.println( );
}
static {
System.out.println("初期化ブロックを実行中");
System.out.println("現在のrun="
+ run);
run = 99;
System.out.println("run=99に変更");
System.out.println("現在のrun="
+ run);
System.out.println("初期化ブロック終了");
System.out.println( );
}
}
public class Init {
public static void main(String[ ]
args) {
Walk w1 = new Walk( );
Walk w2 = new Walk( );
}
}
このアプリケーションには、Walkというクラスが登場する。Walkはコンストラクタと初期化ブロックを持つ。
class Walk {
…
…
Walk( ) { ・・・・・コンストラクタ
…
…
}
static { ・・・・・初期化ブロック
…
…
}
}
コンストラクタと初期化ブロックは、それぞれ自身の名前を出力して、どちらが先に実行されるか分かるようになっている。
また、フィールドrunの値を逐次変更する事で、プログラムがどのように進んでいくかがわかるようになっている。
実行例
$ java Init
初期化ブロックを実行中 ・・・・・最初に初期化ブロックを実行
現在のrun= 12
run = 99に変更
現在のrun= 99
初期化ブロック終了
コンストラクタを実行中 ・・・・・w1のコンストラクタ
現在のrun= 99
run= 500に変更
現在のrun= 500
コンストラクタ終了
コンストラクタを実行中 ・・・・・w2のコンストラクタ
現在のrun= 500
run= 500に変更
現在のrun= 500
コンストラクタ終了
$ _
この実行例を見ながら、いくつかポイントを追ってみる。まずプログラムの実行順序であるが、これは、
初期化ブロック
↓
コンストラクタ
のように実行されているのがわかる。クラスWalkのインスタンスをw1とw2の2つ生成している。
それに伴ってコンストラクタも2回実行されている。
しかし、初期化ブロックの方は、最初のコンストラクタが実行される前に1回だけ実行されているのも確認できる。
次にフィールドrunの値を追ってみると、
12→99→500
のようになっている。12は宣言時の初期化による。
class Walk {
public static int run = 12; ・・・・・ここ
…
}
99に変更しているのは、初期化ブロックである。また500に変更しているのは、コンストラクタである。
このようにクラス変数は、宣言時に最初の初期化が行われ、その後、初期化ブロック→コンストラクタの順に変更する事が出来る。
フィールドの参照
初期化ブロックは、インスタンスを生成することなく実行する事が出来る。(初期化ブロックを含むクラスを指定してアプリケーションを実行した時)
また、前項のサンプルの実行例を見てもわかるように、コンストラクタよりも先に初期化ブロックが実行される。
このように、初期化ブロックが実行される段階では、まだインスタンスが生成されていない。
この為、初期化ブロックの中で普通のフィールド(インスタンス変数)を参照する事は出来ない。
初期化ブロックが参照できるのは、クラス変数だけである。
例えば、前項のクラスWalkは、インスタンス変数とクラス変数の2つのフィールドを持っていた。
class Walk {
int time; ・・・・・・・・・・・・・・・インスタンス変数
public static int run = 12; ・・クラス変数
…
…
}
このうち、初期化ブロックで参照できるのは、クラス変数のrunの方だけである。
インスタンス変数のtimeを参照するとエラーになる。例えば、
static { ・・・・・・・初期化ブロック
time = 100; ・・インスタンス変数を参照
…
}
のようなコードを書くと、次のようなコンパイルエラーになる。
Init.java:19: static でない変数 time を static コンテキストから参照する事はできません。
time = 100;
^
エラー1個
クラスメソッド
フィールドにクラス変数があるようにメソッドにもクラスメソッドがある。クラスメソッドの特色は次の2つである。
・ インスタンスを生成することなく使用する事が出来る。
・ そのクラスのインスタンスフィールドを参照する事は出来ない。
フィールドと同様にメソッドもstaticで修飾する事ができる。例えば次のメソッドshowは、staticで修飾されている。
class Foo {
public static void show( ) { ・・・・・staticで修飾されている
…
…
}
}
staticで修飾されているメソッドをクラスメソッドという。クラスメソッドは、クラス変数と同様、インスタンスを生成しないでも使う事が出来る。
次にクラスメソッドを使ったサンプルを示す。
サンプル:GMethod.java
//
//GMethod.java---クラスメソッド
//
class Foo {
int val = 400;
static int sval = 500;
public static void show( ) {
System.out.println("staticメソッドを実行中");
System.out.println("sval =
" + sval);
}
}
public class GMethod {
public static void main(String[ ]
args) {
Foo.show( );
}
}
このサンプルでは、mainメソッドの中でクラスメソッドshowを使っている。
このとき、showが所属するクラスFooのインスタンスを生成していない事に注意。
public static void main(String[ ] args)
{
Foo.show( );
}
実行例
$ java GMethod
staticメソッドを実行中
sval = 500
$ _
クラスメソッドが一般のメソッドと異なる点がもう1つある。それは、普通のフィールド(インスタンス変数)を参照できないという事である。
クラスメソッドが参照できるフィールドは、staticで修飾されたフィールド―すなわちクラス変数だけである。
例えば、クラスFooには、インスタンス変数valとクラス変数svalの2つのフィールドふぁある。
class Foo {
int val = 400; ・・・・・・・・・・・・・・・インスタンス変数
static int sval = 500; ・・・・・・・・・クラス変数
public static void show( ) { ・・・・クラスメソッド
… ・・・・・・・・・・・・・・・・・・この中でvalを参照する事はできない
…
}
}
このうちクラスメソッドの中で参照できるのはクラス変数svalだけである。
もし、インスタンス変数のvalを参照すると、コンパイルエラーになってしまう。
mainメソッドの修飾
これまでmainメソッドの修飾を深く考えないで使ってきた。
クラスメソッドを理解した今、mainメソッドの全ての修飾を理解する事ができる。
次が一般的なmainメソッドの書式である。
public static void main(String[ ] args)
{
…
…
}
これらのうち、publicはアクセス修飾子でmainを他のクラスから使えるようにする。
次にstaticである。これはmainメソッドをクラスメソッドにしている。
アプリケーションを実行すると、mainメソッドの文から実行を開始される。
ところが、その時mainメソッドを含むクラスのインスタンスを生成していない。
mainメソッドがそのクラスのインスタンスを生成しないでも実行出来るのは、mainメソッドがクラスメソッドだからである。
次へ