第 2 章 Java 言語の基本的な文法 I 5
3.3 Course.java ( 履修科目と成績のクラス )
// 履修科目と成績を定義するクラス public class Course {
// フィールド // 科目名
private String title;
// 成績
private int degree;
// コンストラクタ
public Course( String title, int degree ) { this.title = title;
this.degree = degree;
}
// ゲッターメソッド // 科目名を返す
public String getTitle() { return title;
}
// 点数を返す
public int getDegree() { return degree;
} }
3.2.2 ファイルとクラスの関係
前期では、プログラムは1つのクラスで出来ていて、1プログラム・1ファイル・1クラスが基本でした。クラスの 中は、当初 main メソッド単独でしたが、後半、幾つかのメソッド(正確にはクラスメソッド)が入るものも出ていま した。
一方、後期はそのほとんどが複数のクラスからなるプログラムになります。また、1つのファイルに複数のクラスが 入る場合も出てきます。そこで、ファイルとクラスのルールをまとめておきましょう。
• Javaプログラムは複数のクラスの集合体で、そのうち1つのみmain メソッドを持つ。
• 1つのクラスを複数のファイルに分断することはできない。
• 1ファイル内には、複数のクラスを入れることが出来るが、アクセス修飾子public が付いたクラスは1つの ファイルに高々1つしか入れられない。
• ファイル名は中に入るクラス名のいずれとも異なっていて構わないが、一般には、その中の中心となるクラス の名前と一致させる。ただし、publicなクラスがある場合は、そのクラス名とファイル名は一致させねばなら ない。
慣例としては、1ファイルに1クラス!がベストと言われていますが、このテキストでは、小さなプログラムの場合に (どのプログラムも小さいけどね)、ひとまとめに1つのファイルに押し込むこともあります。
まずは、コンストラクタから見ていきましょう。クラスを記述してオブジェクト(ここでは、学生)を定義しても、具 体的なインスタンス (ここでは、野田さん)を作らなければ、一般には何もできません。って、いつものようにちょっ とウソです (前期のプログラムではインスタンスなんか作らずにプログラム出来てました)。これは、オブジェクト指 向のプログラムとしてはインスタンスを作らなければ始まらない!ということです 。
前期のプログラムはいずれもオブジェクト指向のプログラムとは言えませんでした。Java言語はオブジェクト指向 言語ですが、必ずしもオブジェクト指向で作らなければならない!というわけではありません。ものによっては、オブ ジェクト指向にすることの出来ない現象もあります。ただ、オブジェクト指向の方法論でプログラムを作ると、とても わかりやすいプログラムになる場合が世の中には多いのです。
Student クラスで定義された学生オブジェクト、その実現値「野田さん」を定義する処理を、別のプログラム
StudentExample.java のmain メソッド内に作ることにします(ソースコード3.2参照)。これまで、同じクラス内 の別のメソッドを呼び出すプログラムは作りましたが、別のクラス(それも別のファイルのクラス)を利用するプログ ラムは初めてですね。
コンパイラがプログラムをコンパイルする際、定義されたクラスの記述を、まず同じファイル内で探します。そこに 見つからなかった場合、今度は同じパッケージ内を探します。そこでも見つからなかった場合は、パッケージの外に探 しに行きます。今回の場合、同じパッケージsection0302内のファイルStudent.javaの中にStudentクラスを見 つけることが出来ました。もし同じ名前のクラスが複数ある場合にどうするかなどについては、後で触れます。
このプログラム中のStudent noda = new Student(1419001, "野田 愛");の右辺でStudent型インスタンス
「野田さん」を作っています。そして、左辺の参照変数noda に「野田さん」インスタンスの格納(先頭)アドレスを代 入することで、変数 nodaからその情報を参照できるようにしています(図3.2参照)。変数nodaはStudent型イン スタンスを参照しているので、Student型で宣言(Student noda)します。
ところで、第2章では基本型の8 種類を学びましたが、ちゃんと覚えています?
そして、文字列型 String と基本型の配列int[] などを学びました。String name = "野田 愛";やint[] a = {1, 2, 3}; とした場合、変数name やaには何が入るんでしたっけ?
右辺の文字列や配列のデータが格納されているメモリー上でのアドレスでしたね。こうした変数を参照変数と呼びま した。
これと同じで、変数noda には、右辺new Student(1419001, "野田 愛");で作成されたインスタンスの情報が 置かれているメモリ上のアドレスが代入されており、変数 nodaも参照変数と言えます。
後期では、このようにクラス型のオブジェクトを使ったプログラムが次々出てきます。Student がその最初の例 です。
newキーワードに続くStudent(1419001, "野田 愛")の部分は、クラスStudentの中の「コンストラクタ」に対 応しています。この時、クラス内には、引数の個数と型の一致するコンストラクタを用意しておかねばなりません。こ の場合、第 1引数にint型の整数、第2 引数に文字列を引数とするコンストラクタが必要です。今回Studentクラ ス内に置かれたコンストラクタはそれにフィットしており、よって、インスタンスを作る作業でこのコンストラクタが 実行されます(というか、コンストラクタを用いずにインスタンスは作れません)。
コンストラクタはメソッドと似たものですが、幾つかの違いがあります。
• コンストラクタは新たなインスタンスを作成するときにのみ使われる
• コンストラクタ名はそのコンストラクタが入っているクラス名と同じでなければならない。 従って、コンスト ラクタ名は大文字で始めるパスカル形式である
“野田 愛” private name
public void addRecord( Record ) public void printRecords() private void printAverage() Student 型インスタンス noda
0
private countCourses 1419001
private idNo 参照
Course[ ] course
図3.2 インスタンス「野田さん」
• コンストラクタは戻り値を持てない(voidも書かない)
コンストラクタによってメモリー内にインスタンスが生成されると、その中にはそのインスタンスの属性値を入れる フィールドとそのインスタンスの機能を表すメソッドが全て用意されます (図3.2参照)。なお、コンストラクタはそ のインスタンスを作るときのみ動くものなのでインスタンス内には記憶されません。またstatic修飾子の付いた変数 やメソッド(クラスフィールド・クラスメソッドと言う)は別の場所に記憶されるので、入っていません (後で説明)。 一般に、コンストラクタは、引数に与えられた値をフィールドに初期値として代入する処理を行います。このコン ストラクタでも、this.idNo = idNo; や this.name = name; などで初期値の代入がされていますね。以下では、
Eclipseでの表示に合わせて文字に色を付けてみました。こうすると、同じ名前の変数でも実は違うものを指している
ことがわかります。
ソースコード3.4 Eclipseでの表示 // フィールド
private int idNo; // 学籍番号 private String name; // 氏名
private int countCourses; // 履修科目数
private Course[] cours; // 履修科目の成績情報の配列
private static final int MAXCOUNT COURSES = 100; // 取得可能な科目数の最大 // コンストラクタ
Student( int idNo, String name ) { this.idNo = idNo;
this.name = name;
this.countCourses = 0;
this.cours = new Course[MAXCOUNT COURSES];
}
代入文this.idNo = idNo; の両辺にidNo という変数がありますが、この2 変数は色が異なっています。周辺の 変数の色と比較して気づくと思いますが、左辺の idNoが1 行目でprivate int idNo; と宣言している「フィール ドとしてのidNo」で、右辺のidNoは「コンストラクタの引数のidNo」ですね。引数に与えられた値をフィールドに 代入するという式なわけです。
ところで、コンパイラが2 変数の違いを区別できている理由は、何でしょう。それは、左辺に付いたthis.です。
このthis.は、このコンストラクタで作成されるインスタンスを指す単語です。つまり、this.idNo(インスタンス
が持つidNo)ですから、「インスタンスのフィールドとしての変数」というわけです。このthis.が無ければ、コン
パイラは 2つを区別できません。
もちろん、コンストラクタの引数の変数名はフィールド名と同じでなければならない!というルールはありません。
例えば、このコンストラクタの引数をStudent( int idNum, String sName )と書き換えれば、変数名としての衝 突が無くなるので、this.を外して idNo = idNum;と書いてもコンパイラはthis.の無いidNo をフィールドとし てのidNoと解釈して、問題なく翻訳してくれます。(以下参照)。
ソースコード3.5 this. の省略 private int idNo; // 学籍番号
private String name; // 氏名
private int countCourses; // 履修科目数
private Course[] cours; // 履修科目の成績情報の配列
private static final int MAXCOUNT COURSES = 100; // 取得可能な科目数の最大 // コンストラクタ
Student( int idNum, String sName ) { idNo = idNum;
name = sName;
countCourses = 0;
cours = new Course[MAXCOUNT COURSES];
}
ただ、和田は this.の省略の目的で変数名を変えるのは本末転倒と思う人なのと、一般の変数とフィールドとを常 に区別したいという目的も含めて、フィールド変数には(変数名の衝突が無くとも)なるべくthis.を付けるようにし ています。例えば this.countCourses = 0; や this.course = new Course[MAXCOUNT_COURSES]; には変数名 の衝突が起きないので、もともとthis.を省略しても良いのですが、和田は作為的に付けています。そうすれば、こ れらの代入文がインスタンスのフィールドへの代入文だ、ということが色分けされていなくても分かりますよね。な
お、this.を付ける付けないでコンパイル結果は変わらないので、実行に違いは出てきません。
コンストラクタはクラスに 1つだけとは限りません。例えば、学生のオプション情報(省略可として)として性別や 年齢が追加されたとします。その場合、次のようにインスタンスの作り方がいろいろ出てくるでしょう。それに合わせ てコンストラクタも複数必要になります。
ソースコード3.6 (改) StudentExample.java (複数のコンストラクタが必要) package section0302;
// 学生クラスを利用して成績処理を行うクラス public class StudentExample {
public static void main(String[] args) { // 野田さんインスタンスを生成
Student noda = new Student(1419001, "野田 愛");
// 野田さんの履修科目と成績を登録
noda.addRecord( new Course("プログラミング", 85) );
noda.addRecord( new Course("微積分", 77) );
noda.addRecord( new Course("線形代数", 96) );
Student kagura = new Student( 1419002, "神楽 健一", 19 ); // 年齢が追加 // 登録結果を出力
noda.printRecords();
kagura.printRecords();
} }
こんなとき、それぞれにフィットするコンストラクタを用意してやらねばいけません。コンストラクタの名前はクラ ス名と一緒なので、第2 章で「同じ名前のメソッドを複数持つ場合の条件」というのを学んだことを思い出して下さ い。あの場合と同様に、コンストラクタも次のルールが満たされれば共存できます。
• 引数の個数が違う
• 引数の個数が同じでも、いずれかの型が異なる
つまり、コンストラクタの引数が (String 氏名, double 体重) と (String 氏名, double 身長)は共存でき ませんが (何故ですかね?)、(String 氏名, double 体重)と (String 氏名, int 年齢) は共存できます。ただ、
Student kagura = new Student("神楽 健一", 80);と書いたら、年齢 80歳のおじいちゃん学生が出来てしまい ますね。たぶん体重?なんだろうけど、小数点を忘れた、そんな想定外の値も考慮してプログラムは作成しないといけ ません。こうした問題は、基本型の値を引数に与えると起きる問題です。体重も身長も年齢も、値ではなくオブジェク トにすると、例えば Weightクラス、Heightクラス、Ageクラスなどのインスタンスとして与えれば、問題がなくな ります。つまり、Student(String 氏名, Age 年齢)とStudent(String 氏名, Weight 体重)なるコンストラク タを作ります。もちろん、Ageや Weight なるクラスも定義しないといけませんが、そうするとStudent kagura
= new Student(1419003, "神楽 健一", 80); ではなく Student kagura = new Student(1419003, "神楽 健 一", new Weight(80));なんて与え方が強制できて、80が体重であることがプログラム上でも明示され、入力ミス がなくなります。こうした発想も「オブジェクト指向」の恩恵なのですが、まだ難しいですかね。
最初は、そんなに凝らずに基本形データを与える方法で進めていきます。なお、このようにクラス内にコンストラクタ を複数持ったり、同じ名前の(ただし、引数が異なる)メソッドを複数持ったりすることを「オーバーロード(overload)」 すると言います。
コンストラクタにはインスタンスを作るときにやらねばならないことを記述します。一般にフィールドの初期値を与 えることが多いのですが、そこでは適切な値であるかをチェックする処理も必要でしょう。例えば、学籍番号に負の数 は許されませんし、特定の範囲の番号だけが許されているはず。従って、コンストラクタの中で引数に与えられた値が 適切であるかをチェックし、可能なら修正もしくはエラーメッセージを返すなどの処理もしてやらねばなりません。そ うした処理もコンストラクタの役目です。
コンストラクタはその中で、他のメソッドやコンストラクタを利用することもできます。先のStudentクラスに年 齢を含むコンストラクタを追加してみましょう。
ソースコード3.7 (改) Student.java (コンストラクタのオーバーロード) package section0302;
// 学生を定義するクラス public class Student {
// フィールド
private int idNo; // 学籍番号 private String name; // 氏名 private int age; // 年齢
private int countCourses; // 履修科目数
private Course[] course; // 履修科目の成績情報の配列
private static final int MAXCOUNT_COURSES = 100; // 取得可能な科目数の最大 private static final int UNDEFINED_IDNO = 0; // 学籍番号未定義の値
private static final String UNDEFINED_NAME = ""; // 氏名未定義の値 private static final int UNDEFINED_AGE = -1; // 年齢未定義の値 // デフォルトコンストラクタ
public Student() {
this.idNo = UNDEFINED_IDNO;
this.name = UNDEFINED_NAME;