目的
個々のクラスのインスタンスに所属するインスタンス変数・インスタンスメソッドとは違う、クラスに所属しているクラス変数・クラスメソッドについて理解し、利用出来る様にする。
内容
Dayクラス、MyscheduleTesterクラスにstaticなメンバ(フィールド、メソッド)を追加する。また、それに応じてクラス初期化子も利用する。
ソースプログラム
以下にDayクラス、MyScheduleクラス、MyScheduleTesterクラスのソースプログラムを以下に示す。
Dayクラス
ackage lesson8;
import java.util.Random;
public class Day {
private int year =1;
private int month=1;
private int date=1;
//staticなフィールド
private static int m1;
private static int d2;
//クラス初期化子
static{
Random rand=new Random();
System.out.printf(“ちなみに今年2013年”);
m1=rand.nextInt(6)+7;
d2=rand.nextInt(31)+1;
if(m1==9||m1==11){
hantei();
}
System.out.printf(“%02d月%02d日は予定が入っています。ここには予定は入れられません!!”,m1,d2);
System.out.println(“”);
}
//コンストラクタ
public Day() {
System.out.println(“Day()が呼び出されました”);
}
public Day(int year) {
this.year=year;
System.out.println(“Day(int year)が呼び出されました”);
}
public Day(int year,int month) {
this(year);this.month=month;
System.out.println(“Day(int year,int month)が呼び出されました”);
}
public Day(int year,int month,int date){
this(year,month);this.date=date;
System.out.println(“Day(int year,int month,int date)が呼び出されました”);
}
public Day(Day d){
this(d.year,d.month,d.date);
System.out.println(“Day(Day d)のコピーコンストラクタが呼び出されました”);
}//Day型の変数b(仮引数ではない)
//年・月・日を取得
public int getYear(){
return year;
}
public int getMonth(){
return month;
}
public int getDate(){
return date;
}
//年・月・日を設定
public void setYear(int year){
this.year=year;
}
public void setMonth(int month){
this.month=month;
}
public void setDate(int date){
this.date=date;
}
//年・月・日を設定する独自のメソッド
public void set(int year,int month,int date){
this.year=year;
this.month=month;
this.date=date;
}
//曜日を求めるメソッド
public int dayOfWeek(){
int y=year;
int m=month;
if(m==1||m==2){
y–;
m+=12;
}
return (y+y/4-y/100+y/400+(13*m+8)/5+date)%7;
}
//日付dと等しいか
public boolean equalTo(Day d){
return year==d.year&&month==d.month&&date==d.date;
}
//文字列表現を返却
public String toString(){
String[] wd={“日”,”月”,”火”,”水”,”木”,”金”,”土”};
return String.format(“%04d年%02d月%02d日(%s)”,year,month,date,wd[dayOfWeek()]);
}
//staticなメソッド
public static int hantei(){
Random rand=new Random();
do{
if(d2<31){
return d2;
}
d2=rand.nextInt(31)+1;
}while(true);
}
}
MyScheduleクラス
package lesson8;
public class MySchedule {
private Day playDay;//いつ
private String place;//どこで
private int time;//およそ何時間
private String play;//何をするのか
//コンストラクタ
public MySchedule(Day playDay,String place,int time,String play ){
System.out.println(“あ”);
this.playDay=new Day(playDay);
System.out.println(“い”);
this.place=place;
this.time=time;
this.play=play;
}
public Day getPlayDay(){
return new Day(playDay);
}
public void kaku(){
System.out.println(“場所:”+place);
System.out.println(“なん時間するのか:”+time+”時間”);
System.out.println(“なにをするのか:”+play);
}
}
MyScheduleTesterクラス
package lesson8;
import java.util.Scanner;
public class MyScheduleTester {
//staticなフィールド
private static int counter;
//クラス初期化子(mainメソッドの中はだめ!!)
static{
System.out.println(“2013年今年7月以降の予定を入力します”);
}
//staticなメソッド
public static void kaisuu(){
Scanner stdIn=new Scanner(System.in);
System.out.println(“先程入れた予定を今年あと何回したいか?”);
counter=stdIn.nextInt();//staticなメソッドはstaticなフィールドにアクセスできる
System.out.println(MyScheduleTester.counter);//呼び出しできる(同一クラス)
}
//mainメソッド
public static void main(String[] args){
Scanner stdIn=new Scanner(System.in);
Day z=new Day();
System.out.println(“年:”); int y=stdIn.nextInt();
System.out.println(“月:”); int m=stdIn.nextInt();
System.out.println(“日:”); int d=stdIn.nextInt();
System.out.println(“どこで:”); String place=stdIn.next();
System.out.println(“何時間:”); int time=stdIn.nextInt();
System.out.println(“何をするか:”); String play=stdIn.next();
MySchedule MS=new MySchedule(new Day(y,m,d), place, time, play);
MS.kaku();
System.out.println(“やる日:”+MS.getPlayDay());
kaisuu();
System.out.println(MyScheduleTester.counter);//呼び出しできる(同一クラス
//クラス型のインスタンス配列
System.out.println(“日付の個数:”);
int n=stdIn.nextInt();
Day[] a=new Day[n]; //要素数n個のDay型配列
for(int i=0;i<a.length;i++){
a[i]=new Day(y,m,d);
}
for(int i=0;i<a.length;i++){
System.out.println(“a[“+i+”]=”+a[i]);
}
Day day1=new Day(y,m,d);
System.out.println(“day1 = “+day1);
Day day2=new Day(day1);//Dayクラスのコピーコンストラクタがある
System.out.println(“day2をday1と同じ日付として作りました。”);
System.out.println(“day2 = ” + day2);
if (day1.equalTo(day2))
System.out.println(“day1とday2は等しいです。”);
else
System.out.println(“day1とday2は等しくありません。”);
}
}
実行結果
3のソースプログラムの実行結果を以下の図1、2に示す。


考察
今回のプログラムはクラスが3つあるが、呼び出される順としては、mainメソッドをもつMyScheduleTesterクラス。MyScheduleTesterクラスのフィールドには、private static int counter;というstaticがついたフィールドがあるが、これはクラス変数というもので、個々のインスタンスがもつデータではなく、クラスの全インスタンスで共有する変数である。クラス変数は、前述通り、個々のインスタンスではなく、クラスに所属するのでクラス変数にアクセスするには基本は「クラス名.フィールド名」で出来る。「クラス型変数名.フィールド名」でアクセスも出来るが、staticがついていないフィールドと見た目が同じになり、分かりにくくなる為あまり使わない。また、MyScheduleTesterクラスのメソッドには、public static void kaisuu(){・・・・・}というstaticのついたメソッドがあるが、これは、クラスメソッドというもので、特定のインスタンスではなくクラス全体に関わる処理や、そのクラスに所属する個々のインスタンスの状態とは無関係な処理を行ってくれる。また、今回は新しくクラス初期化子というものをプログラムに入れたが、これはその名前が示す通り、「クラスが初期化される」際に実行される。クラスが初期化されるとは、そのクラスのインスタンスが生成されるときやそのクラスのクラスメソッドが呼び出される時やそのクラスのクラス変数に値が代入される時やそのクラスの定数でないクラス変数の値が取り出される時などだ。クラス変数を初期化する為に必要な処理は、このクラス初期化子で行うと良い。
話を戻してプログラムの流れを見ていくと、MyScheduleTesterクラスが利用される時点で、MyScheduleTesterクラスの初期化子が呼ばれ「2013年今年7月以降の予定を入力します」が出力される。そして、Day z=new Day();によってDayクラスのインスタンスが呼び出されるので、この時にDayクラスの初期化子が呼ばれ、7月以降ですでに予定が入っている日を乱数を利用して出している。月は、rand.nextInt(6)で0~5の乱数を出し、それに+7をすることにより7~12月をランダムで出せる。また、日はとりあえず一番長い31を出したいので、rand.nextInt(31)により0~30の乱数を出し+1にすることにより1~31日の日付が出せる。しかし、9、11月は30日までしかないので、もし9、11月で31日が出てしまったらいけない為、if文とhanteiメソッドで9、11月の日に31日が入らない様にプログラムを作った。もし31日だった場合は、d2に再度乱数を入れ直すようにした。ここでhanteiメソッドはstaticが付いているので、クラスメソッドである。クラスメソッドは、インスタンス変数にはアクセスできないがクラス変数にアクセス出来る。今回d2は、クラス変数なので、エラーはなく正常にアクセス出来た。If文を抜け出すとSystem.out.printf(“%02d月%02d日は予定が入っている。ここには予定は入れられません!!”,m1,d2);によりm1,d2が出力される。
MyScheduleTesterクラスに戻り、次に年を入力するint y=stdIn.nextInt();月を入力するint m=stdIn.nextInt();日を入力するint d=stdIn.nextInt();場所の「どこ」でを入力するString place=stdIn.next();何時間を入力するint time=stdIn.nextInt();何をするかを入力するString play=stdIn.next();を処理する。place 、playは文字列を入力する為String型である。そして、これらを引数にとるMyScheduleクラスのインスタンス化、MySchedule MST=new MySchedule(new Day(y,m,d), place, time, play);を生成する。引数の最初にnew Day(y,m,d)と書いてあるが、これはDay型のインスタンスをnewによって生成する式だ。生成したインスタンスへの参照がコンストラクタに渡されることになる。インスタンスの生成により、MyScheduleクラスのコンストラクタ
public MySchedule(Day playDay,String place,int time,String play ){
this.playDay=new Day(playDay);
this.place=place;
this.time=time;
this.play=play;
}
が呼び出される。ここでMyScheduleクラスのメソッドにDay型の変数 playDayがある。これは、予定日を格納するためのフィールドがplayDay。このフィールドの型はクラス型だ。Day型のインスタンスではなく、インスタンスを参照する変数である。
MyScheduleクラスのコンストラクタのthis.playDay=new Day(playDay);はコンストラクタで日付を設定する部分。ここでは、new演算子とコピーコンストラクタを用いて予定日のインスタンスを生成している。仮引数playDayに受け取った日付のコピーを作って、そのコピーへの参照をフィールドplayDayに代入している。その為、フィールドplayDayは、仮引数に受け取った日付のコピーを参照することになる。この様になっていることは実行結果でも確認出来る。まず、仮引数playDayに受け取った日付のコピーを作った部分はコンストラクタDay(int year)が呼ばれ、次にDay(int year,int month)が呼ばれ、最後にDay(int year,int month,int date)が呼ばれて終了となっている。ここで、this(…)を使っているが、これをコンストラクタの先頭に書くと同一クラス内のコンストラクタの呼び出しが出来る。仮引数playDayに受け取った日付のコピーを作り終わったら、MyScheduleクラスのコンストラクタの内部に行く。これは、System.out.println(“あ”);が出力された時に確認可能だ。次のthis.playDay=new Day(playDay)では、コンストラクタDay(int year)が呼ばれ、次にDay(int year,int month)が呼ばれ、最後にDay(int year,int month,int date)が呼ばれて、最後にコピーコンストラクタ、Day(Day d)が呼ばれる。もし、this.playDay=new Day(playDay)の部分をnewによってインスタンスを生成せず、this. playDay=playDay;という単純な代入になっていると、参照先が同じになってしまい、新しい日付インスタンスが作られなくなってしまう。これが終わるとMyScheduleクラスのコンストラクタの内部に戻る。これは、System.out.println(“い”);が出力されたときに確認出来る。その後は、place,time,playに先ほど、入力した数、文字列が入力され、このコンストラクタは終了で、MyScheduleTesterクラスに戻り、MS.kaku();が呼ばれplace,time,playの出力がされる。そして、その次は、MS.getPlayDay()によってgetPlayDay()メソッドが呼び出される。このメソッドのreturn文は、new Day(playDay);と書いてあるが、ここの部分を、play;と書いてしまうと、メソッドgetPlayDay()は、やる日フィールドそのものへの参照を返却してしまい、返却された参照を通じて、外部からやる日フィールドの値が書きかえられる可能性がある。new Day(playDay)にすると、メソッドgetPlayDay()は、やる日フィールドのコピーへの参照を返却する。返却された参照を通じて、外部からやる日フィールドの値が書き変えられることは無くなる。
続いて、kaisuuメソッドが呼ばれる。このメソッドはstaticがついているのでクラスメソッドだ。counterはクラス変数なので正常にアクセス出来る。counterには、先程の予定を今年あと何回したいか?を入力する。そして、MyScheduleTester.counter(クラス名.フィールド名)でcounterを出力します。終わるとkaisuuメソッドを抜けmainメソッドに戻る。そしてSystem.out.println(MyScheduleTester.counter)と書きcounterを出力しているが、ここで分かったことがcounterはprivateだが、同一クラスかつインスタンス変数ではなく、クラス変数なので、クラス名.フィールド名とやり、出力することが出来ると分かった。
その後のプログラムは、まずクラス型のインスタンス配列のプログラムで、ここで重要なのがクラス型インスタンスの配列を利用するには、クラス型変数の配列を生成したあとに、個々の要素のインスタンスを生成しなければならないこと。続いて、クラス型変数の比較のプログラムで、Day day2=new Day(day1);の部分で実行結果に書いてある通り、ここでコピーコンストラクタが呼び出される。受け取った日付dのフィールドd.year,d.month,d.dateの値をコピーし、日付を初期化してくレル。
今回のプログラムは3つのクラスに分けているが、この3つを1つのプログラムにまとめて書く時は、2つ以上のpublicクラスを宣言出来ない。
まとめ
staticを付けて宣言されたフィールド・メソッドは、クラス変数・クラスメソッドとなる。クラス変数は、個々のインスタンスがもつデータではなく、そのクラスに所属している全インスタンスで共有するデータを表すのに適している。インスタンスの個数とは無関係に、クラス変数は1個のみ存在する。そのアクセスは、クラス名.フィールド名で行う。クラスメソッドは、特定のインスタンスではなく、クラス全体に関わる処理や、クラスインスタンスの状態とは無関係な処理を実行するのに適している。その呼び出しは、クラス名.メソッド(・・・)によって行う。クラスメソッドからは、同一クラスのインスタンス変数と、インスタンスメソッドにはアクセスできない。
コメントを残す