staticって何?
staticの概念がしっかり理解できるように、また使い所がよく分かるように例を出しながらゆっくり説明していきます。
staticじゃないメンバ(インスタンスメンバ)
今回はstaticの意味を解説したいと思います。
まだstaticの意味がよく分かっていない人も、書いたことは何回もあるんじゃないでしょうか?この講座で使っているpublic static void main(String[] args)にも出てきますよね。このmainメソッドはstaticなメソッドです。
今回はstaticな変数とメソッドを扱います。このstaticを理解するには、まずstaticではない変数やメソッドがどんなものなのかを考えると分かりやすいと思います。新しい概念を知るときは対立する概念とセットで考えると理解しやすいものです。
なのでまずは非staticなメンバとはどういうものかを理解する為に以下のクラスを見てください。
Human.java
public class Human{
String name; // メンバ1
int birthday; // メンバ2
int manpukudo; // メンバ3
Human(String name, int birthday, int manpukudo){ // コンストラクタ1
this.name = name;
this.birthday = birthday;
this.manpukudo = manpukudo;
}
Human(String name, int birthday){ // コンストラクタ2
this(name, birthday, 50);
}
Human(String name){
this(name, 0, 50); // コンストラクタ3
}
Human(){ // コンストラクタ4
this("不明", 0, 50);
}
void eat(){ // メンバ4
this.manpukudo += 60;
}
}続けて読んで頂いている人は知ってると思いますが、これは人間オブジェクトを作る為のHumanクラスです。
このクラスには、自分で宣言した4つのメンバと4つのコンストラクタがあります。
4つのメンバというのは、メンバ変数のnameとbirthdayとmanpukudo。
それにメンバメソッドであるeat( )です。
このHumanクラスで宣言したフィールドとメソッドにはstaticが付いていません。定義するときにstaticと書いていない変数やメソッドは非staticになります。非staticなメンバというのは、インスタンス固有のメンバだということです。
「インスタンス固有の」という表現は全く難しく考える必要はありません。「名前」はもちろんそのインスタンスが持つ名前だし、「生年月日」も「満腹度」も「食べるという動作」も全て、それぞれのインスタンスに関連付いているというのが感覚的に分かるかと思います。この感覚がよく分からないという人はオブジェクトって何?から読み直すことをオススメします。
このように非staticな変数・メソッドというのはインスタンスに関連したものなので、インスタンス変数(インスタンスフィールド)・インスタンスメソッドという風に表現します。両方合わせてインスタンスメンバとも言います。
staticな変数(クラス変数)
非staticなメンバがインスタンスに関連付いたものであるなら、逆にstaticなメンバとはどのようなものなのか?例としてこのクラスに一つstaticなメンバを加えたいと思います。
Human.java
public class Human{
static int count_Human = 0;
String name;
int birthday;
int manpukudo;
//以下省略
}staticなcount_Humanというint型変数を加えてみました。上記のように変数の宣言の前にstaticを付ければstaticな変数になります。
count_Humanはその名の通りHumanのカウントを表す変数です。つまり何人のHumanオブジェクトが作られたかをカウントする変数です。初めは0人なので0で初期化しています。Humanオブジェクトの数をカウントできるように、コンストラクタにちょこっと細工をします。
Human.java
public class Human{
static int count_Human = 0;
String name;
int birthday;
int manpukudo;
Human(String name, int birthday, int manpukudo){ // コンストラクタ1
this.name = name;
this.birthday = birthday;
this.manpukudo = manpukudo;
count_Human++;
}
Human(String name, int birthday){ // コンストラクタ2
this(name, birthday, 50);
}
Human(String name){
this(name, 0, 50); // コンストラクタ3
}
Human(){ // コンストラクタ4
this("不明", 0, 50);
}
//以下省略
} 一つ目のコンストラクタに「count_Human++」を足しました。これでコンストラクタが呼ばれたら、つまりHumanインスタンスが作られる度に、count_Humanの値が1増えます。
1つ目以外のコンストラクタが呼ばれた時は増えないのか?と心配していただいた方、大丈夫です。2つ目、3つ目、4つ目のコンストラクタが呼ばれたらthis( )で1つ目のコンストラクタが呼ばれるようになっていますので、newする時に引数がいくつであろうと結局1つ目のコンストラクタが呼ばれてカウントは1増えます。(参考:〔コンストラクタとは〕 〔this( )の意味〕)
この「count_Human」つまり「Humanの人数」はインスタンス固有の値ではないというのは感覚的に分かりますよね?「名前」や「満腹度」はノブオ固有のものですが、「Humanの人数」はノブオ固有のものとは言えないですね。ノブオの人数ではないのですから。
このcount_Humanはインスタンスに紐付いたものではなく、一体何に紐付いた値と言えるか??
Humanクラス自体に紐付いた値と言えるのではないでしょうか?つまりstaticな変数count_HumanはHumanクラス固有の値と言えます。
例えばHumanクラスとは別にDogクラスがあったとしてDogクラスにもそのインスタンスの数をカウントするstaticな変数count_Dogがあったとすると、それはDogクラスの固有の値と言えます。Humanクラス内のstaticなメンバは、(そのインスタンスではなく)Humanクラス自体に紐付いているメンバであり、Dogクラス内のstaticなメンバは、(そのインスタンスではなく)Dogクラス自体に紐付いているわけです。
このようにstaticなメンバというのは、インスタンス固有ではなくクラス固有のメンバという意味でとらえることができます。
なので、staticな変数、staticなメソッドのことをクラス変数・クラスメソッドと表現します。両方合わせてクラスメンバ、あるいは静的メンバとも言います。静的というのはstaticのことです。
インスタンスに紐付いた値(名前や生年月日など)と、クラス自体に紐付いた値(作られたインスタンスの数など)の違いをしっかり理解する必要があります。
クラス変数(staticな変数)にアクセスする
オブジェクトって何? 〔メンバ変数とは〕で説明した通り、インスタンス変数(非static変数)にアクセスするには、
インスタンスを表す変数名.メンバの形でした。具体的には、
HumanTest.java
Human human1 = new Human();
human1.name = "ノブオ";
System.out.print(human1.name);↑このようにHuman型の変数human1を宣言して、そこにnewで作ったインスタンス(の参照)を代入することで、変数human1がHumanインスタンスを表す変数になり、「human1.name」の形で、「human1の名前」にアクセスできるので、そこにノブオを代入したり、その値を出力したりできるわけです。
しかし、クラス変数(static変数)にアクセスする方法はちょっと違います。
実はhuman1.count_Humanという書き方も文法上はできます。ただし、human1専用の値になるわけではなく、同じクラス変数にアクセスします。インスタンス固有の値と混同しないように、static変数はクラス名を使ってアクセスする書き方にそろえましょう。
count_Humanが表しているのは、Humanクラスからこれまでに作ったインスタンスの数です。Humanクラスそのものの数でも、特定の人の人数でもありません。
インスタンスに属するパラメータではなく、クラス自体に属するパラメータというのはそういうことです。繰り返しになりますが、そういうクラスに属するパラメータをクラス変数、static変数、静的変数(全部、同義)と言うわけです。
ではクラス変数(static変数)にアクセスするにはどうするか?
クラス名.メンバ この形で、そのクラスのstaticなメンバにアクセスできます。クラスに属するパラメータなので、「クラス名.」の形になるのは意味的に理解できますね。
では具体的にやってみます。
HumanTest.java
Human human1 = new Human();
System.out.print(Human.count_Human);これで実行すると「1」と出力されます。Humanインスタンスが一つ作られたことによってcount_Humanの値は1になっているからです。
↓こんな風にもできます。
HumanTest.java
public class HumanTest{
public static void main(String[] args){
System.out.println(Human.count_Human); //①
Human human1 = new Human();
System.out.println(Human.count_Human); //②
Human human2 = new Human();
System.out.println(Human.count_Human); //③
}
}①の時点ではまだHumanオブジェクトは作られていないので「count_Human」の値は「0」、②の時点では「1」、③の時点では「2」になるので、HumanTestのmainメソッドを実行すると、結果はコンソールに表示されます。
0
1
2という風に出力されます。
ところで、①に注目してもらいたいのですが、クラス変数(static変数)に関してはそのクラスのインスタンスが存在しなくても、「Human.count_Human」というように、その値にアクセスすることができます。逆にインスタンス変数の場合は、もちろんインスタンスが存在しなければそのパラメータ自体存在しないので、インスタンスを作ってからでないとアクセスできません。
当然ですね、「human1.name」という風にインスタンス変数にアクセスできるのは、そのインスタンスが格納されたhuman1という変数が存在するからです。もちろんインスタンスメソッドについても同様です。
今度はstaticなメソッドを追加してみましょう。
Human.java
static int getCount() {
return count_Human;
}Human.getCount()と呼べば、それまでに作った人数を取得できます。特定の一人に食事をさせるhuman1.eat()と違い、呼び出し先のインスタンスは必要ありません。
全てのインスタンスに共通する値
もう一つ、微妙にニュアンスが違うstaticの使い方として、全てのインスタンスに共通する値として使う方法があります。
例えば、eat()メソッドを呼べるのは、つまり食事を摂れるのは1日3回までというルールを課すとしましょう。この”3”という値は各インスタンスが持つべき値ではなく全人類共通の値です。なのでdailyMealLimitをstatic変数としてクラスに持たせます。一方、今日何回食べたかを表すdailyMealTimesは、一人ひとり違うのでインスタンス変数です。
Human.java
public class Human{
static int dailyMealLimit = 3; // 全員共通の食事回数の上限
int dailyMealTimes = 0; // この人が今日食事した回数
int manpukudo;
// その他のフィールド・コンストラクタは省略
void eat(){
if (this.dailyMealTimes < Human.dailyMealLimit){
this.dailyMealTimes++;
this.manpukudo += 60;
}
}
}このように、各人の状態はインスタンス変数、全員で共有するルールの値はstatic変数とすると、役割がはっきりします。
なお、staticは「変更できない」という意味ではありません。変更できない値にしたい場合に使うのはfinalです。ここではまず、staticは、各インスタンスではなくクラスに属するメンバを表すことと押さえてください。
まとめ & 注意
staticのニュアンスがなんとなく理解できましたでしょうか?
一言でいうと、クラスに属するのがstatic、インスタンスに属するのが非staticだということです。
そして一つ注意。
staticなメソッドから、インスタンス変数やインスタンスメソッドを、対象のインスタンスを指定せずに使うことはできません。参照を使ってhuman.nameやhuman.eat()のように指定すればアクセスできます。
「this」を使って、そのインスタンス変数にアクセスすることが出来るのはメンバメソッドって何?で説明しましたが、それはインスタンスメソッド内(あるいはコンストラクタ)だからこそ可能なんです。
「このインスタンス」を意味する「this」は、インスタンスメソッド内で使うからこそ、「このインスタンス」がどのインスタンスを指しているか分かるのであって、インスタンスとは無関係に呼ばれるstaticメソッド内から「this.name」とやっても、誰の名前やねん!ってことになるのでコンパイルエラーになります。
staticメソッドには、呼び出しの対象となる「このインスタンス」がありません。インスタンスのデータを使いたいときは、どのインスタンスなのかを参照で指定する必要があります。
例えば、
Main.java
public class Main{
int a;
public static void main(String[] args){
a = 10;
}
}これはコンパイルエラーになります。aはMainクラスのインスタンス変数ですが、ここではどのMainインスタンスのaなのかを指定していません。
これもアウトです。↓
Main.java
public class Main{
int a;
public static void main(String[] args){
this.a = 10;
}
}staticメソッド内ではthisを使えません。別の場所でMainのインスタンスを作っていたとしても、このmainメソッドに「このインスタンス」が結び付くわけではないからです。
mainメソッドを持つクラスも、newでインスタンスを作れます。その参照を使ってアクセスすれば、次のように書けます。
Main.java
public class Main{
int a;
public static void main(String[] args){
Main main = new Main();
main.a = 10;
}
}このように参照を指定すれば、staticメソッドからもインスタンス変数を使えます。ただし、インスタンスを作ってもstaticメソッド内でthisが使えるようになるわけではありません。
逆にstaticな変数なら、staticなメソッドからでもアクセスできます。staticな変数というのはインスタンスに関係なく存在できるものなので、同じくインスタンスとは関係ないstaticなメソッドから触るのは全然OKです。↓
Main.java
public class Main{
static int a;
public static void main(String[] args){
a = 10;
}
}これでも大丈夫です。↓
Main.java
public class Main{
public static void main(String[] args){
int a;
a = 10;
}
}これなら、「a」はMainクラスのメンバとしてではなく、mainメソッド内のローカル変数として宣言されていることになるので、もちろんそのメソッド内では自由に触れます。
ローカル変数とは、メソッドやコンストラクタ、if文などのブロック内で宣言する変数です。ローカル変数は、宣言した位置からそのブロックの終わりまでのみ利用できます。
クラス直下に宣言するフィールドやメソッドにはstaticを付けられますが、ローカル変数にstaticは付けられません。ローカル変数は一時的な処理に使われるだけであって、クラスのものでもインスタンスのものでもありませんので。
次回は、アクセス修飾子とゲッターについての説明です。
質問・コメント
読み込み中この記事についての質問や、学習して気づいたことをお寄せください。
この記事のコメント
旧サイトに寄せられたコメントも当時の投稿内容のまま掲載しています。現在の記事では変更している内容や、旧環境についてのやり取りも含まれます。
コメントを読み込んでいます。