第 2 章 Java 言語の基本的な文法 I 5
3.13 Member.java ( 会員情報 )
public class Entry {
private int memberCount;
private Member[] member;
private static int memberNo = 1000; // このクラスフィールドには final が付いていない public Entry( int n ) {
member = new Member[ n ];
memberCount = 0;
}
public void add(String name, int age) {
memberNo++; // クラスフィールドの値をインスタンスが変更している
member[memberCount++] = new Member( memberNo, name, age );
}
public void print() {
System.out.println( "現在登録者数:" + memberCount );
for(int i=0; i<memberCount; i++) { member[i].printRecords();
} } }
ソースコード3.13 Member.java (会員情報) package section0302;
public class Member { private int idNo;
private String name;
private int age;
Member( int idNo, String name, int age ) { this.idNo = idNo;
this.name = name;
this.age = age;
}
void printRecords() {
System.out.printf( "%03d %s (%2d)%n", idNo, name, age );
} }
問題 3.2.4. 上のプログラムで、年齢が未定義である場合(値が-1とする)の定数と年齢なしのコンストラクタを追加 して、名前無しでも登録できるようにしなさい。出力も「年齢不詳」と出力されるように変えなさい。
作られたインスタンスのフィールドの値を利用するときは、大きく2種類の方法があります。
• そのフィールドの値を直接利用する
• ゲッターを利用する
まず、そのフィールドの値を直接利用する場合、インスタンスフィールドとクラスフィールドで書き方が異なります。
• インスタンスフィールドの場合「インスタンス名.フィールド名」、例えばnoda.idNoやkatushika.name(野 田さんの学籍番号、葛飾くんの名前) という感じですね。int idNo = noda.idNoとすれば野田さんの学籍番 号が変数idNoに代入されます。
• クラスフィールドの場合「クラス名.フィールド名」、例えばStudent.UNIV_NAME 学生全員の大学名というわ けです。
インスタンスからクラスフィールドを直接利用する、つまり noda.univNameという使い方もアリではあるのです が、オブジェクト指向の考え方としてはあまり薦められていません。あくまで「クラスのフィールド」なんでしょうね
(Eclipseでは黄色の警告ランプが付きます)。一方、その逆Student.idNoはエラーになります(学生の学籍番号はク
ラス内で一意に決まらないからですね)。
もう一つの利用方法、ゲッターを利用する方法は、先のPoint クラスにおける getX() とgetY() のようなゲッ ターメソッド、例えばgetIdNo()を作って利用する方法です。ところで、なんでわざわざ、こんなメソッドを用意し て使うのでしょう?
先のPointクラスのフィールドとゲッターを抜き出してみると、次のようになっています。
ソースコード3.14 Pointクラスのゲッター package section0302;
// 平面上の点のためのクラス public class Point {
private double x;
private double y;
:
// ゲッター
public double getX() { return this.x;
}
public double getY() { return this.y;
} }
注目すべきは、フィールドxやyに付いている修飾子privateです。これは、Pointクラスの外側から、そのフィー ルドの値を直接利用することを「拒否する」修飾子です。つまり、Point p = new Point();とインスタンスを作成 させても、Pointクラスの外では p.x = 1.5; p.y = -0.3;のような直接的な値の代入や参照 (変数の値を見るこ と)はさせないぞ!という修飾子です。
こうした修飾子を「アクセス修飾子(access modifier)」と言いますが、データの入った変数をむやみに外部から覗か れたり、変更されたりすることを防いでいます(アクセス修飾子はこの後でまとめて解説します)。預金口座の金額が 入った変数int myMoneyの値を外部プログラムで勝手に0にされては困りますよね。そんな場合に、変数にprivate 修飾子を付けて、外部から直接アクセスされることを防止しています。
ただ、それでは正規な方法での値の代入などができません。そこで、その値を変更するメソッド (セッター)や、そ の値を取り出すメソッド(ゲッター)を限定公開し、それのみを「利用可」にするわけです(もちろん、そのメソッド内 には正規利用者のアクセスか否かをチェックする機構も付けておかねばなりません)。限定されたメソッドを経由しな いでこのフィールドにアクセスできないようにして、安全を確保する!というわけです。
もう一つ、セッターやゲッターを作る目的が、privateにしたフィールドの名前を自由に変えられる、という利点 です。もしフィールドを外部に自由に使わせるように設定すると、名前の変更はユーザに各自のプログラムの変更を強 制してしまうことになります。代わりに、セッターなどを使えば、(その名前を変更してはなりませんが)フィールド名 の変更や内部構造の変更を外部に影響与えることなく行うことができます。これが次の章で詳しく学ぶ「カプセル化」
です。
一般に、フィールドには全てprivateを付け、ゲッターやセッターはpublic(自由に利用可能)で公開せよ!と言わ れます。
問題 3.2.5. ソースコード3.1のStudent.javaにおける次の変数のスコープ範囲をそれぞれ述べよ。
1. フィールドidNo
2. コンストラクタの引数idNo
3. メソッドprintRecords()の繰り返しカウンタi 4. メソッドprintAverage()の変数sum
この問題より、フィールドの値は、(コンストラクタで初期値を代入したり、メソッドでその値を利用したり)そのク ラス内の全ての要素からアクセスが可能であることがわかります。一方、メソッドの内部で宣言され、そのメソッドの 使用が終わると同時にメモリから消える変数 (iや sum)をローカル変数(local variable) と言いますが、それらはメ ソッドブロックの外部からは利用できませんので、メソッドが違えば同じ変数名を用いることもできるわけです。
public static void main(String[] args) { for(int r=1; r<=10; r++) {
printCircleArea( r );
} }
// 半径 radius の円の面積 area を出力するメソッド static void printCircleArea( int r ) {
double area = r * r * Math.PI;
System.out.printf( "半径=%2d の円の面積は、%8.4f です%n", r, area );
} }
こ の メ ソ ッ ド の 本 来 の 呼 び 出 し 方 は「 ク ラ ス 名 .printCircleArea( r )」で な け れ ば な り ま せ ん 。つ ま り 、 CircleArea2.printCircleArea( r ); が本来の書き方です。しかし、CircleArea2 クラスから見ると、自分の 中にあるメソッドを使うのに毎回自分の名前を付けねばならないというのはちょっと面倒ですよね。そこで、問題(名 前の重複)が起きない限りは、自分のクラス名は省略可能となっているのです。
もちろん、他のクラスから使う場合は、クラス名が必要になります。例えば、Math.cos(double)はMath クラス のクラスメソッドを使っているわけで、Math. は省略できませんでした。しかし、それも面倒な場合がありますよね。
その場合は、import文を用いることになります(import文は後で詳しく)。
クラスメソッドはクラスの持つメソッドなので、インスタンスを作らなくとも利用することができます。一方、イン スタンスメソッドは、インスタンス個々が持つメソッドですから、インスタンスを作って初めて使うことが出来るよ うになります。また、インスタンスごとに処理結果が違ってきます。学生の成績情報プログラムで、Studentクラス のprintRecords()メソッドは、インスタンスメソッドでした(staticが付いていないですね)。同じメソッドでも、
noda.printRecords(); と利用すれば野田さんの情報が出力され、kagura.printRecords();と利用すれば神楽く んの情報が出力されるわけです。
問題 3.2.6. 以下のプログラムはオブジェクト指向とは言い難い(だって、どこにもインスタンスが発生していないか
ら)。そこで、Humanクラスを作り、名前を記憶するString型フィールドnameを与え、コンストラクタに文字列"太 郎"を与えてHumanインスタンス taroを作り、taroのインスタンスメソッドhello に時刻を渡して以下と同じよ うな挨拶の処理を行うように、TaroExample.java を改良(?) しなさい。(ちょっと無理っぽいオブジェクト指向で すが)
ソースコード3.16 TaroExample.java (クラスを作ってみる)