2019/02/06
sponsored link
staticじゃないメンバ(インスタンスメンバ)
今回はstaticの意味を解説したいと思います。
まだstaticの意味がよく分かっていない人も、書いたことは何回もあるんじゃないでしょうか?mainメソッドを書く時に出てきますよね。mainメソッドはstaticなメソッドなんです。
staticなメソッドとかstaticな変数とか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( )です。
これらのメンバは全てstaticではありません。どこにもstaticとは書いていないですよね?staticな変数やメソッドにはその宣言部分に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変数)にアクセスするには、
インスタンスを表す変数名.メンバ
の形でした。具体的には、
Human human1 = new Human(); human1.name = "ノブオ"; System.out.print(human1.name);
↑このようにHuman型の変数human1を宣言して、そこにnewで作ったインスタンス(の参照)を代入することで、変数human1がHumanインスタンスを表す変数になり、「human1.name」の形で、「human1の名前」にアクセスできるので、そこにノブオを代入したり、その値を出力したりできるわけです。
しかし、クラス変数(static変数)にアクセスする方法はちょっと違います。
もし、同じように「human1.count_Human」とやってしまうと「human1の人数」という意味になってしまいます。意味がおかしいのが感覚的に分かりますよね?Humanオブジェクトをhuman1、human2、human3、・・・と作っていっても、「human1の人数」は1人です。
count_Humanは「human1の人数」ではなく、あくまで「Humanの人数」です。大文字になっていることに注意してほしいのですが、つまりcount_HumanとはHumanクラス(オブジェクト)の数です。
インスタンスに属するパラメータではなく、クラス自体に属するパラメータというのはそういうことです。繰り返しになりますが、そういうクラスに属するパラメータをクラス変数、static変数、静的変数(全部、同義)と言うわけです。
ではクラス変数(static変数)にアクセスするにはどうするか?
クラス名.メンバ
この形で、そのクラスのstaticなメンバにアクセスできます。クラスに属するパラメータなので、「クラス名.」の形になるのは意味的に理解できますね。
では具体的にやってみます。
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」になるので、このクラスを実行すると、
という風に出力されます。
ところで、①に注目してもらいたいのですが、クラス変数(static変数)に関してはそのクラスのインスタンスが存在しなくても、「Human.count_Human」というように、その値にアクセスすることができます。逆にインスタンス変数の場合は、もちろんインスタンスが存在しなければそのパラメータ自体存在しないので、インスタンスを作ってからでないとアクセスできません。
当然ですね、「human1.name」という風にインスタンス変数にアクセスできるのは、human1というインスタンスが存在するからであって、human1インスタンスが無ければアクセスのしようがありません。もちろんインスタンス変数だけでなくインスタンスメソッドについても同様です。
全てのインスタンスに共通するパラメータ
もう一つ、微妙にニュアンスが違うstaticの使い方として、全てのインスタンスに共通するパラメータとして使う方法があります。
例えば人間の腕は2本、足は2本です。これはインスタンス固有のパラメータというよりも全てのインスタンスに共通するパラメータです。
そのようなパラメータを定義したい場合は以下のようにすることができます。
Human.java
public class Human{ static int count_Human = 0; static int count_arms = 2; static int count_legs = 2; String name; int birthday; int manpukudo; //省略 }
こうしておけば、以下のような使い方ができます。
HumanTest.java
class HumanTest{
public static void main(String[] args){
Human human1 = new Human("Nobuo");
System.out.println(human1.name + "の腕は" + human1.count_arms + "本です");
}
}
HumanTest.javaの実行結果
Nobuoの腕は2本です
ご覧のように、ここではhuman1.count_armsという風にインスタンス変数のようにアクセスしています。
先程の例のcount_Humanに関しては、各インスタンスとは直接関係のない値ですが、この場合は各インスタンスに共通するパラメータとして使っているのが分かると思います。
count_Humanの値は、インスタンスとは関係がないものなのでHuman.count_Humanという風にクラス名を使ってアクセスしていました。
一方、count_armsの値は、human1.count_armsという風にインスタンスが持っている値としてアクセスしています。
意味的にはこのアクセスの仕方で正しいと言えますが、実はstaticな変数へアクセスする方法はどちらでも構いません。human1.count_Humanとしても、Human.count_armsとしても文法上は全く問題ありません。
要はstatic変数は、アクセスする方法は「クラス名.」でも「インスタンス名.」でもどちらでも問題ないものの、インスタンスごとに固有の値を持つことはできない変数だと言えます。
逆に言えば、全てのインスタンスに(必ず)共通な値を持てる、あるいは、クラスにとって固有の値を持てるということです。
まとめ & 注意
staticのニュアンスがなんとなく理解できましたでしょうか?
一言でいうと、クラスに属するのがstatic、インスタンスに属するのが非staticだということです。
そして一つ注意。
クラスメソッド(staticなメソッド)から、インスタンス変数やインスタンスメソッド(非staticなメンバ)にアクセスすることはできません。
「this」を使って、そのインスタンス変数にアクセスすることが出来るのはメンバメソッドって何?で説明しましたが、それはインスタンスメソッド内(あるいはコンストラクタ)だからこそ可能なんです。
「このインスタンス」を意味する「this」は、インスタンスメソッド内で使うからこそ、「このインスタンス」がどのインスタンスを指しているか分かるのであって、インスタンスとは無関係に呼ばれるstaticメソッド内から「this.name」とやっても、誰の名前やねん!ってことになるのでコンパイルエラーになります。
インスタンスの有り無しに関係なく使えるクラスメソッドからインスタンス変数にアクセスしようとしても、一体どのインスタンスのパラメータにアクセスしようとしているのか判断のしようがありませんからね。
例えば、
Main.java
public class Main{ int a; public static void main(String[] args){ a = 10; } }
こんなことはできません。コンパイルエラーになります。「a」は非staticなので、このMainクラスのインスタンス変数として宣言されています。それをstaticメソッドであるmainメソッドから触ることはできません。
これもアウトです。↓
Main.java
public class Main{ int a; public static void main(String[] args){ this.a = 10; } }
上述した通り、staticメソッド内で「this」を使うことはできません。インスタンスを作ってもいないのに「このインスタンス」も何もありません。
mainメソッドがあるクラスは特別扱いしてしまいがちですが、mainメソッドを持つクラスでもnewすることでインスタンスを作ることもできます。なので、以下のように書けば一応コンパイルは通ります。
Main.java
public class Main{ int a; public static void main(String[] args){ Main main = new Main(); main.a = 10; } }
こんなことを実際することがあるのか知りませんが。。もちろんインスタンスを作ろうが、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メソッド内のローカル変数として宣言されていることになるので、もちろんそのメソッド内では自由に触れます。
ローカル変数というのはこのようにメソッド内や、あるいはfor文やif文のブロック内で宣言された変数のことを言います。ローカル変数のスコープ(存在範囲)はその宣言されたブロック内だけです。
逆に言えば、クラスブロック直下に宣言されているのがメンバ変数です。混乱しないように整理して覚えておきましょう。
ちなみにローカル変数にstaticを付けることはできません。staticか非staticかというのは、そのクラスのメンバに限り設定できるということです。
次回は、アクセス修飾子とゲッターについての説明です。
今だけ→転職できなければ全額返金の「エンジニア転職保証コース」
絶対エンジニアになる!→テックエキスパート
フリーランスエンジニアの収入例を見てみる→レバテックフリーランス
コメント
すごくわかりやすかったです!
プログラム経験は一応あるものの、staticが何なのかイメージが掴めず困っていたところにこのページに出会い、理解することができました。
プログラムに対する苦手意識が消えました!
by 匿名 2015/09/27 16:07
前半のときのように一例を出して実行などして画面をたくさんみせてほしかったです。最後のほうで理論的な話が多くなってしまって前半に比べてわかりづらかったです。続編期待しています。
by とおりすがり 2015/11/22 20:57
ご意見ありがとうございます。
by Nobuo@管理人 2015/11/23 22:17
めちゃくちゃ分かりやすかったです。
ありがとうございました。
理解出来ました。
by dfd 2018/04/16 09:14
たぶん、どんな参考書でもこのあたりから書き手は説明下手になり始める。そしてこのサイトでも同様だと思う。自分が期待するわかりやすさではなかった。わかりにくいとは言わないが、決して初心者にとってわかりやすいとは言えないと思う。
by 匿名 2019/08/26 13:37
いつもお世話になっております。
staticとは何か?
の解答が
mainメソッドがstaticなメソッド
初心者の頭には?マークが立って、そのあと、「static,static,static…」と続いたので
「ふぁーーーーー」ってなりました。
staticと非staticの対比を画像で明確にして、そこから説明してくれるとわかりやすい。
一旦、飛ばします。
by saitalmon 2019/09/16 22:34
数個前の記事から読んでます。
例文を追いながら、説明・解説を読んで、びっくりするほど理解できました。
ありがとうございましたm(_ _)m
by 匿名 2019/10/30 17:41
単純に読みづらく頭に入ってこないです。
初心者が文字だけで理解できるとは思わないで図解などを交えて解説してほしいです。
by 匿名 2020/03/31 16:10
他と比べると分かりやすくして頂いてとても助かり有り難いです。ただ表示されるコード、説明が部分的であり、全体として何をしているのかが分かりづらいのが難点です。
by 匿名 2020/06/16 19:37