Revised: Feb./14th/2003: Since: Dec./30th/2001
修飾子に static
キーワードを持つ変数やメソッドを静的変数、静的メソッドと呼びます。"static field", "static method" を直訳しただけですね。インスタンス化しても、これらの実体は一つだけです。従って、インスタンス化しなくても、クラス名.変数名
/クラス名.メソッド名()
または オブジェクト名.変数名
/オブジェクト名.メソッド名()
で利用できます。
もうすこし説明します。
static
修飾子がついていないフィールド(メンバー変数)やメソッドはインスタンス固有のものであり、インスタンス変数、インスタンス・メソッドと呼びます。インスタンスとはメモリ上に存在する値のことです。
クラスはオブジェクトの型定義であり、コンストラクタ呼び出しによってインスタンス化することでオブジェクトが作成されます。元になるクラスのメソッドやフィールドは、個々のオブジェクトごとに用意されます。変数に関して言えば、値を保存するメモリ領域がオブジェクトごとに別個に用意されると言うことです。
例えば、「銀行口座」クラスをインスタンス化して、「スガイの口座」、「サトウの口座」を作ったとします。このとき、「銀行口座」クラスで定義されたフィールド「預金残高」は、「スガイの口座」と「サトウの口座」で全く独立にアクセスできなければ、おかしなことになります。メソッドに「払い戻し」が用意されていれば、そのメソッドもオブジェクトごとに独立して動作するはずです。「サトウの口座」の「払い戻し」メソッドによって、「預金残高」変数が変更されたときに、「スガイの口座」の「預金残高」が変更されていたら、大変なことですね?通常は、フィールドの値とメソッドの動作はオブジェクトに固有のものであり、それこそがインスタンスそのものです。
これが通常のオブジェクトのメソッドとフィールドです。このようなインスタンス・メソッド、インスタンス変数は、クラスがインスタンス化されるたび(コンストラクタ呼び出しのたび)にメモリ上に作成されます。
static 修飾子を指定したフィールド(メンバー変数)やメソッドは、クラス固有でオブジェクト間で共有されるものであり、クラス変数、クラス・メソッドと呼びます。また、静的変数、静的メソッドとも呼ばれます。これらはインスタンス化しなくても利用できます。また、インスタンス化しても、実体として用意されるメモリ領域は、そのクラスをインスタンス化して生成した全てのオブジェクト間で共有されるのです。
静的変数の例を考えてみましょう。円周率 π = 3.14 をフィールドとして定義したとします。このクラスをインスタンス化するとき、オブジェクトごとに個別に変数 π を用意して良いものでしょうか?全てのオブジェクトで変数 π は共有するべきでしょう。このようなときに、静的変数として static
宣言します。
或いは、「総売上」変数をフィールドとして定義したとしましょう。オブジェクトとして「スガイ」、「サトウ」などが作られるとします。「スガイ」の売上も、「サトウ」の売上も、共通の「総売上」変数に計上したいとします。このとき、「総売上」変数がインスタンスごとに別個に用意されてはならないので、「総売上」変数の定義時に、静的変数として static
宣言しておきます。
因みに、 main()
メソッドはインスタンス化されないで直接実行される可能性があるため、通常は static
宣言しておきます。
コンストラクタは本質的に static です。コンストラクタは、クラスをインスタンス化してオブジェクトを作成するときに使うものです。オブジェクトを生成する前に実行することになるので、 static でなければならないのです。
具体例を見ておきます。静的変数、静的メソッドは、インスタンス化しなくても使えます。インスタンス化しても、オブジェクト固有のものではなく、全てのオブジェクト間で共有されます。
StaticDemo01.java
:
class StaticDemo01 {
public static void main(String[] args) {
//参照型変数宣言
StaticVar sugai;
StaticVar satou;
//コンストラクタ呼び出し
sugai=new StaticVar();
satou=new StaticVar();
System.out.println("-----一回目------");
//メソッド呼び出し
sugai.setUriage(150);
satou.setUriage(80);
System.out.println("sugai: 150");
System.out.println("satou: 80");
System.out.println("sugai.x: " + sugai.x);
System.out.println("satou.x: " + satou.x);
System.out.println("sugai.y: " + sugai.y);
System.out.println("satou.y: " + satou.y);
System.out.println("-----二回目------");
//メソッド呼び出し
sugai.setUriage(90);
satou.setUriage(120);
System.out.println("sugai: 90");
System.out.println("satou: 120");
System.out.println("sugai.x: " + sugai.x);
System.out.println("satou.x: " + satou.x);
System.out.println("sugai.y: " + sugai.y);
System.out.println("satou.y: " + satou.y);
}
}
class StaticVar {
//フィールド
int x; //インスタンス変数
static int y; //静的変数
//メソッド
void setUriage(int x) {
this.x += x;
this.y += x;
}
}
C:\Java>javac StaticDemo01.java C:\Java>java StaticDemo01 -----一回目------ sugai: 150 satou: 80 sugai.x: 150 satou.x: 80 sugai.y: 230 satou.y: 230 -----二回目------ sugai: 90 satou: 120 sugai.x: 240 satou.x: 200 sugai.y: 440 satou.y: 440
動作は確認できたでしょうか? static
変数 y
が、オブジェクトによらず、一つの実体が参照されていることを確認してください。ここではオブジェクトの変数と同じように記述して参照しましたが、クラス名でも参照できます。 main()
メソッドだけもう一度書いてみます:
public static void main(String[] args) { //参照型変数宣言とコンストラクタ呼び出し StaticVar sugai = new StaticVar(); StaticVar satou = new StaticVar(); System.out.println("-----一回目------"); //メソッド呼び出し sugai.setUriage(150); satou.setUriage(80); System.out.println("sugai: 150"); System.out.println("satou: 80"); System.out.println("sugai.x: " + sugai.x); System.out.println("satou.x: " + satou.x); System.out.println(" Total: " + StaticVar.y); System.out.println("-----二回目------"); //メソッド呼び出し sugai.setUriage(90); satou.setUriage(120); System.out.println("sugai: 90"); System.out.println("satou: 120"); System.out.println("sugai.x: " + sugai.x); System.out.println("satou.x: " + satou.x); System.out.println(" Total: " + StaticVar.y); }
main()
メソッドを上のように書き換えたときの実行結果も、もう一度載せておきます:
C:\Java>javac TestStatic.java C:\Java>java TestStatic -----一回目------ sugai: 150 satou: 80 sugai.x: 150 satou.x: 80 Total: 230 -----二回目------ sugai: 90 satou: 120 sugai.x: 240 satou.x: 200 Total: 440
もっと単純な例を見て見ましょう。
class StaticDemo02 { public static void main(String[] args) { InstanceCounter obj01 = new InstanceCounter(); System.out.println("obj01: " + obj01.counter); InstanceCounter obj02 = new InstanceCounter(); System.out.println("obj02: " + obj02.counter); InstanceCounter obj03 = new InstanceCounter(); System.out.println("obj03: " + obj03.counter); InstanceCounter obj04 = new InstanceCounter(); System.out.println("obj04: " + obj04.counter); } } class InstanceCounter { static int counter; InstanceCounter() { counter++; } }
InstanceCounter
クラスをインスタンス化して4つのオブジェクトを作っています。各々のオブジェクトの counter
変数を出力していますが、これらは static なので、実は同じものを指しています。
C:\java>javac StaticDemo02.java C:\java>java StaticDemo02 obj01: 1 obj02: 2 obj03: 3 obj04: 4 C:\java>
static 修飾されたメソッドは、自分のクラス内のものでも、 static でないメソッドは使うことができません。static 修飾されていないメソッドや変数は、オブジェクトに固有のものとして取り扱われるのですが、 static メソッドからは、どのオブジェクトのものか修飾されていないので分からないからです。より実装的な説明をすると、static メソッド以外のメソッド内に修飾されないメソッド呼び出しがあると、自動的に this が追加されます。一方、static メソッドは this キーワードを持たないのです。
一方、static 修飾されていないメソッドからは、自分のクラス内のメソッドをオブジェクトを指定しないでも利用できます。オブジェクトが省略されているだけで、自分自身が所属するオブジェクトのメソッド/変数だと解釈できるからです。
class StaticDemo03 {
public static void main(String[] args) {
Stuff obj = new Stuff();
obj.staticMaster();
obj.master();
}
}
class Stuff {
static void staticMaster() {
System.out.println("static master");
staticSlave();
// コメントを外すとコンパイルエラー
// slave();
}
static void staticSlave() {
System.out.println("static slave");
}
void master() {
System.out.println("master");
slave(); // this.slave() の省略形
}
void slave() {
System.out.println("slave");
}
}
上のソースコードの Stuff クラス内の staticMaster() メソッドで、slave() メソッド呼び出しのコメントを外すと、以下のようなメッセージでコンパイルエラーが発生します。static メソッドから、自クラス内の非 static メソッドを呼び出すことはできません。
C:\java>javac StaticDemo03.java StaticDemo03.java:14: static でない メソッド slave() を static コンテキストから 参照することはできません。 slave(); ^ エラー 1 個
最後に、 static 初期化子 (static initializer) を紹介します。これは、クラス内部でメソッド外に記述する匿名ブロックのようなものです。形式としては次のようになります。
class StaticDemo {
String str = "通常のメンバー変数";
static {
// static 初期化子
}
void method() {
// 通常のメソッド
}
}
static 初期化子は、クラスがメモリ上にロードされるとき、一回だけ実行されます。その後、いくらインスタンス化されようとも、決して実行されることはありません。
例えば、複雑なメンバーの初期化や、native 宣言されたメソッドの実装を格納したネイティブコードのライブラリのロードなど、当該クラスを利用するための前準備のために使われます。このような書式が文法上正しいということだけ覚えておいてください。