はじめに
こんにちは。
staticがよく分からない人です。なのでstaticの勉強をしました。
「staticってなにがどうなってるの!」と感じている方に向けて、そのしくみについて解説していこうと思います。
動作環境
OS:Windows10 バージョン20H2
言語:Java 8
使用エディタ:Spring Tool Suite 4
1. staticとは?
public static void main(String[] args){}
staticとは変数やメソッドにつける修飾子のことです。
Java使用者なら必ず見るであろう、処理を始めるためのエントリーポイントについている、あれです。
staticを修飾した変数とメソッドは「クラスのインスタンスを生成しなくても呼び出すことが出来る」という特徴があります。
ホントかどうかは試してみないとわからないので試してみましょう。
class Test {
static int staticNum = 0;
int num = 0;
}
このように、Testクラス内にstaticを修飾した変数staticNumと、staticを修飾しない変数numを用意します。
次にインスタンス化を行わないでコンソールに表示した際の、変数staticNumと変数numそれぞれの挙動を見てみましょう。
public class Main {
public static void main(String[] args) {
System.out.println(Test.staticNum); // 実行結果:0
}
}
class Test {
static int staticNum = 0;
int num = 0;
}
public class Main {
public static void main(String[] args) {
System.out.println(Test.num); // 実行結果:エラー
}
}
class Test {
static int staticNum = 0;
int num = 0;
}
とまあ、当然のようにインスタンス化しなかったからエラー...とはならず、staticを修飾した変数staticNumはコンパイルに成功しています。
一方、staticを修飾しなかった変数numはエラーが起きています。
一体全体どうなってるのか...?
2. static領域とヒープ領域
先程の挙動には、staticを修飾したフィールドのアクセスが関係しています。
ここで、絶賛私が勉強中のJavaSilverSE11黒本の解説を恐縮ですがお借りします。
引用:
Javaではプログラムに必要なクラスを読み込んで実行します。クラスファイルを読み込むことを「ロード」と呼びますが、ロード後、クラスファイルの内容はstaticな部分とそれ以外に分離され、それぞれ異なるメモリ空間に配置されます。
つまり、ファイルを実行(ロード)した後にstaticで修飾された変数やメソッドは「static領域」と呼ばれる領域に配置され、それ以外の変数やメソッドは「ヒープ領域」と呼ばれる領域に配置されることになります。
これらの知識を踏まえた上で、先程のMainクラスでTestクラスをインスタンス化し、以下のコードの実行結果を確認してみます。
public class Main {
public static void main(String[] args) {
Test t = new Test();
Test t2 = new Test();
t.staticNum += 1;
System.out.println(t.staticNum); //実行結果:1 ・・・⓵
t2.staticNum += 1;
System.out.println(t2.staticNum); //実行結果:2 ・・・⓶
t.num += 1;
System.out.println(t.num); //実行結果:1 ・・・⓷
t2.num += 1;
System.out.println(t2.num); //実行結果:1 ・・・⓸
}
}
あれま!staticで修飾したstaticNumはインスタンス化したtとt2で値が共有されてるのに、staticで修飾していない変数numは値が共有されてません!なんで!
それは、以下のような動作が行われているからです。
⓵static領域に存在するstaticNumの値「0」に「1」がプラスされてstaticNumの値は「1」
⓶static領域に存在するstaticNumの値「1」に「1」がプラスされてstaticNumの値は「2」
⓷ヒープ領域で新たに生成されたインスタンスt内の変数numの値「0」に、「1」がプラスされてnumの値は「1」
⓸ヒープ領域で新たに生成されたインスタンスt2内の変数numの値「0」に、「1」がプラスされてnumの値は「1」
staticで修飾された変数やメソッドは、インスタンス生成前にstatic領域という特殊な領域に配置され、値が共有されます。
中身が同じであるため、上記のコードのようにインスタンス内で使う場合でも値は共有されています。
一方、ヒープ領域内ではnewされるたびにクラス定義にしたがったインスタンスが生成されるため、値が同じであっても、それぞれのインスタンスによって参照先が異なります。だからnumとnum2の値は共有されていないのです。
↓各領域と配置を図に表すとこんな感じです。
(JavaSilverSE11黒本の図を基に筆者作成)
3. まとめ
・staticとは変数やメソッドにつける修飾子のひとつ。
・クラスファイルをロードした時に、staticで修飾された変数・メソッドは、インスタンス生成前にstatic領域に配置されるため、staticはインスタンスを生成しなくても使用することが出来るという性質を持つ。
・staticで修飾された変数・メソッドは、インスタンスごとの変数・メソッドではないため、その値は共有されている。
おわりに
「staticの値ってなんで共有されてるんだ意味分かんね!!」
...なんて思っていましたが、しくみが分かればすんなり頭に入ってきました。
この記事によって、staticがよく分からない同志が救われることを願います。