クラスにより新しい型をつくる
package j2.lesson01; public class AverageRaw { public static void main(String[] args) { // 各科目の点数 int score0 = 55; int score1 = 95; int score2 = 78; // 得点を表示 System.out.println("国語の得点は" + score0 + "点"); System.out.println("数学の得点は" + score1 + "点"); System.out.println("英語の得点は" + score2 + "点"); // 平均点を計算する double average = (score0 + score1 + score2) / 3.0; // 平均点を表示 System.out.println("平均点は" + average + "点"); } }
このプログラムでは、score0の値が国語の点数であり、score1が数学の点である、といったことが、プログラムの最後の出力の文を読むまで分からない。それがもっとはっきり分かるようにするためには、"国語"という科目名とその点数とを一まとめにしたデータ型を作るのが良い。そのようなデータ型はclassとして作ることが出来る。
package j2.lesson01; public class CourseRecord { // 科目名 String subject; // 点数 int score; }
この書き方はクラスフィールド (クラス変数)の宣言の仕方に似ているが、static をつけていないことに注意すること。このようなclassを使うと、先述のプログラムは以下のように書くことが出来る。
package j2.lesson01; public class AverageClass { public static void main(String[] args) { // 国語の点数記録を作る // (CourseRecord型のデータを一つ作り、そのIDをcr0に格納する) CourseRecord cr0 = new CourseRecord(); cr0.subject = "国語"; cr0.score = 55; // 数学の点数記録を作る CourseRecord cr1 = new CourseRecord(); cr1.subject = "数学"; cr1.score = 95; // 英語の点数記録を作る CourseRecord cr2 = new CourseRecord(); cr2.subject = "英語"; cr2.score = 78; // 平均点 double average = (cr0.score + cr1.score + cr2.score) / 3.0; // それぞれの科目について表示する System.out.println(cr0.subject + "の得点は" + cr0.score + "点"); System.out.println(cr1.subject + "の得点は" + cr1.score + "点"); System.out.println(cr2.subject + "の得点は" + cr2.score + "点"); // 平均点を表示 System.out.println("平均点は" + average + "点"); } }
このプログラムの最初の
CourseRecord cr0 = new CourseRecord();
は、CourseRecord型 (あるいはCourseRecordクラス) の変数 cr0 を宣言し、その初期値として CourseRecord 型のオブジェクト (CourseRecord型のデータ) を1つ作って与えている (クラス C のオブジェクトを作るときには new C() と書く)。その結果、プログラムの中の世界には次のような箱が用意される。
変数 cr0 の箱にはデータが直接入るのではなく、CourseRecord型のデータ (オブジェクト) を表すIDが入る (この点は配列の場合と同じである。一般にあるクラス C の変数の箱に入るのはそのクラスのオブジェクトのIDである)。
CourseRecord型のオブジェクトは subject と score からなるが、new CourseRecord();ではその場所を作るだけで、まだそれらに値は入っていない (実際には、システムが決めている特定の値 (default value) が初期値として入るが、そのことは考えないほうが良い)。
同様にして cr1 と cr2 の箱が作られる。
その次の
cr0.subject = "国語";
は、cr0 の subject に "国語" を代入する文である。「.」は「の」と読めばいい。もうすこし詳しく言うと、cr0 が参照している CourseRecord 型オブジェクトの中の変数 subject に "国語" を代入する文である。その次の
cr0.score = 55;
はcr0のscoreに55を代入する文である。それらを実行した結果は次のようになる。
実際にはStringもクラスであり、subjectはString型の変数であるから、その箱には "国語" のような文字列が直接入るのではなく、その文字列が入っているオブジェクトの ID が入るのであるが、ここでは見易さのために直接入れておく。
score は int 型の変数であるので score の箱には整数の値 55 が直接入る。cr1とcr2にも同様にしてデータが与えられる。
このようにして作られた CourseRecord 型のオブジェクトは CourseRecord クラスのインスタンス (instance:具体例) と呼ばれる。オブジェクトとインスタンスは同様のものと考えてよい。今の例では、cr0, cr1, cr2 の(それぞれのIDが指す)3つのインスタンスが作られている (たとえば、cr0 は CourseRecord 型の一つのインスタンスを参照しているのであるが、cr0 が一つのインスタンスである、という言い方もする)。
cr0 は subject と score という変数を持ち、cr1 や cr2 も subject と score という変数を持っている。すなわち subject と score という変数はインスタンスごとに別々に存在する。これらの変数はインスタンスフィールド (インスタンス変数) とも呼ばれる。
次の
double average = (cr0.score + cr1.score + cr2.score) / 3.0;
は、それぞれの点数の平均の計算である。
その後にはそれぞれの内容を出力する文があるが、それらは同じパターンである。これについては後ほど触れる。
クラスとインスタンス (オブジェクト)
下記の2つの図形には共通点がある。
異なる形であるが、どちらも三角形という部類 (クラス) に属している。そして、これら2つの図形は、三角形というクラスから具体例 (インスタンス) を挙げたものであるといえる。
Java の class は new クラス名()としてやることによって、そのクラスに対する具体例をひとつ作成できる。この具体例のことをインスタンスまたはオブジェクトと呼ぶ。
先ほどの CourseRecord クラスの例では、CourseRecord クラスに対して cr0 というインスタンスを作成していた。これは「科目の得点」という部類に対して、「国語の得点は55点である」という具体例を一つ示している。また、その具体例を cr0 という箱 (変数) に格納し、後ほどプログラムから操作している。
CourseRecord cr0 = new CourseRecord(); cr0.subject = "国語"; cr0.score = 55;
属性とインスタンスフィールド
三角形などのクラスをJavaで表現したい場合、まず、次のようなことを考える。
- クラスにどのような属性があるか
ここで属性とは、オブジェクト(=インスタンス)に備わっている固有の性質のことを表す。先ほどの2つの三角形の差異は「それぞれの辺の長さ」であった。つまり、三角形であっても、辺の長さが異なれば違うインスタンスであるといえる (もしくは内角の大きさ等でもよい)。
三角形は、三辺の長さを決定すると具体的な形が決まる。ここでは三角形を表現するために、属性として辺の長さ a, b, c を持つということにする。これによって、実際に形を持った三角形を作成することができるようになる。
package j2.lesson01; public class Triangle { // 辺 a の長さ double a; // 辺 b の長さ double b; // 辺 c の長さ double c; }
このように部類を特徴付けるような情報を属性と呼び、クラス に対して属性をインスタンスフィールドとして定義してやることによって、Javaで新しいクラスを作成することができる。
このTriangleクラスを使ってインスタンスを作成する例を示す。
package j2.lesson01; public class CreateTriangles { public static void main(String[] args) { // 三角形 T1 を作る Triangle t1 = new Triangle(); t1.a = 3.0; t1.b = 4.0; t1.c = 5.0; // 三角形 T2 を作る Triangle t2 = new Triangle(); t2.a = 4.0; t2.b = 4.0; t2.c = 4.0; } }
クラスに対してその属性のことを、「has-a 関係にある」と呼ぶことがある。
- 三角形 has-a 辺 a の長さ (三角形は辺 a の長さを持つ)
- 三角形 has-a 辺 b の長さ (三角形は辺 b の長さを持つ)
- 三角形 has-a 辺 c の長さ (三角形は辺 c の長さを持つ)
クラスを作成する際には、そのクラスとhas-a 関係にあるもの (つまり、クラスやインスタンスが持つ属性)について考えてやり、それらをインスタンスフィールドで定義してやるとよい。
このようなプログラムの書き方は、現実世界の物体 (オブジェクト) をカテゴリ分けする方法に非常に似ている。これまでのプログラムの書き方は、三角形を表す場合であれば3つの辺の長さを別々に定義し、それらをプログラム上で三角形のそれぞれの辺の長さだと決めて書く必要があった。
クラスやオブジェクトを用いたプログラミングでは、三角形は Triangle クラスのインスタンスとして扱える。これにより、3辺を意識することなく、三角形としてプログラムを作成できるようになる。
ここで注意すべきことは、クラスを作成する際に必要な情報は、プログラムを書く上で必要な最小限の情報にとどめるべきであるという点である。例えば、「社員」というクラスを定義する際に次のようにする必要はない。
public class Staff { String name; int age; double tall; double weight; char[] DNA; ... }
おそらく、社員というデータにDNAの情報は必要ない。むしろ、部署や階級、勤続年数などの情報のほうが重要であるはずである。そこで、次のように書けばよい。
public class Staff { String name; String department; String degree; int serviceYears; ... }
このように、属性を考える際にはプログラムにあった属性を考える必要がある。
クラスと型
CourseRecord 型のプログラムを紹介する際にも触れたが、作成したクラスはそのまま型として使用できる。型とはこれまでに紹介した int, double, char, boolean, String などのことで、これらに自分で作成した型を加える事になる。
クラスを型として扱えるようになることによって、様々なことができるようになる。例えば、メソッドの引数や戻り値として使うことができる。
プログラム AverageClass では、次のような3行があった。
// それぞれの科目について表示する System.out.println(cr0.subject + "の得点は" + cr0.score + "点"); System.out.println(cr1.subject + "の得点は" + cr1.score + "点"); System.out.println(cr2.subject + "の得点は" + cr2.score + "点");
これは、よく見てみると CourseRecord 型のオブジェクトが持つ「subject」と「score」を同じ形式で表示しているだけである。これを読みやすいように書き換えると、以下のようなプログラムになる。
package j2.lesson01; public class AverageMethod { public static void main(String[] args) { // 国語の点数記録を作る CourseRecord cr0 = new CourseRecord(); cr0.subject = "国語"; cr0.score = 55; // 数学の点数記録を作る CourseRecord cr1 = new CourseRecord(); cr1.subject = "数学"; cr1.score = 95; // 英語の点数記録を作る CourseRecord cr2 = new Cource(); cr2.subject = "英語"; cr2.score = 78; // 平均点 double average = (cr0.score + cr1.score + cr2.score) / 3.0; // それぞれの科目について表示する printScore(cr0); printScore(cr1); printScore(cr2); // 平均点を表示 System.out.println("平均点は" + average + "点"); } // 得点を表示するメソッド public static void printScore(CourseRecord cr) { System.out.println(cr.subject + "の得点は" + cr.score + "点"); } }
printScore(CourseRecord)は、CourseRecord型のオブジェクトを受け取って、その名前と得点を表示するだけのメソッドである。cr0, cr1, cr2 はどれもCourseRecord型であるから、printScoreメソッドに渡すことによってそれぞれの名前と得点を表示することができる。
もう一つの例として、戻り値にTriangle型を使う例を示す。
package j2.lesson01; public class ReturnsTriangle { public static void main(String[] args) { Triangle triangle = createUnitTriangle(); printTriangle(triangle); } // 3辺の長さがすべて1の正三角形を作成する public static Triangle createUnitTriangle() { Triangle unit = new Triangle(); unit.a = 1.0; unit.b = 1.0; unit.c = 1.0; return unit; } // 三角形を表示する public static void printTriangle(Triangle t) { System.out.println("(" + t.a + ", " + t.b + ", " + t.c + ")"); } }
また、自分で作成したクラス型のインスタンスフィールドを作ることができる。
例えば、「三角柱」を表すクラスを作る際、次のような属性を持つことが考えられる。
- 三角柱 has-a 高さ
- 三角柱 has-a 三角形の底面
そこで、三角柱を表すクラス TrianglePole は次のように書ける。
package j2.lesson01; public class TrianglePole { // 高さ double height; // 底面 Triangle base; }
それを扱う例として、以下のようなものがある。
package j2.lesson01; public class TrianglePoleSample { public static void main(String[] args) { TrianglePole pole = new TrianglePole(); // 高さ5.0 pole.height = 5.0; // 底面は長さ2.0の正三角形 pole.base = new Triangle(); pole.base.a = 2.0; pole.base.b = 2.0; pole.base.c = 2.0; } }
ここで、「pole.base.a」は「ポールの底面の辺a」と読める。他にも、次のような書き方でもよい。
// 底面は長さ2.0の正三角形 Triangle t = new Triangle(); t.a = 2.0; t.b = 2.0; t.c = 2.0; pole.base = t;
前者の例では底面を作ったあとに、底面の属性を書き換えていた。後者の例では、三角形を作り、その属性を書き換えたあとに、三角柱の底面という属性を、新しく作った三角形に設定している。
どちらも同じ結果になるのでどちらを使ってもかまわないが、どちらも使えるようにしておくこと。
また、他の型と同様にクラス型の配列を作成することもできる。
Triangle[] ts = new Triangle[5]; for (int i = 0; i < ts.length; i++) { ts[i] = new Triangle(); ts[i].a = i + 2; ts[i].b = i + 3; ts[i].c = i + 4; }
オブジェクトの参照渡し
オブジェクトは ID を用いて管理されるため、コピーする際には注意する必要がある。
Triangle t1 = new Triangle(); t1.a = 3.0; t1.b = 4.0; t1.c = 5.0; Triangle t2 = t1; t2.b = 9.5;
上記のようなプログラムの断片を実行すると、t1.b の値まで変化してしまうことになる。考え方は配列のときと同様で、t1 と t2 が指し示すオブジェクトの実体が同じものになってしまっている。つまり、t1 の各要素に加えられた変更は t2 の各要素にも反映してしまうことになる。
上記の理由から、メソッドを呼び出すときに実引数にオブジェクトを指定した場合、参照渡しとなる。つまり、呼び出した側の実引数と、呼び出された側の仮引数は、同一のオブジェクトを指し示すことになる。