プログラムは「メソッド」で分割して記述することができますが、プログラムが大きくなると、何百、何千、といったメソッドになります。ファイルを階層的に記録するフォルダがあるように、「クラス」はメソッドや変数を階層的に管理できるプログラムの構造です。
-
クラスの定義と利用
「クラス」は特定の対象(Object:もの)をターゲットを対象にプログラムされるため、クラスを利用したプログラムはオブジェクト指向型プログラミングとも呼ばれます。「クラス」の定義は以下のようにクラス名をつけ、ブロック{
}の中にいくつかの変数とメソッドを定義します。
class クラス名 { 変数定義; メソッド定義;}
少し、特殊なメソッドとして、コンストラクタを定義できます。「コンストラクタ」を定義しておくと、new で「オブジェクト」を作成するとき、「コンストラクタ」への引数を利用した初期化が可能です。「コンストラクタ」は引数の数や型で複数定義可能です。
定義したクラスを利用する場合、まず、オブジェクトを作成します(「オブジェクト」はインスタンスとも呼ばれますが、ここでは Java流 の表現でオブジェクトと呼びます)。
クラス名 オブジェクト名 = new クラス名(引数);
オブジェクトを作成すると、オブジェクト名.メンバー名 でクラスのメンバー(変数やメソッド)を参照できます。
クラスの定義と利用
たとえば、点を表現するクラス Point を次のように定義します。Point は点の位置を示す座標 px,py と 点を小さな円で表示する show() メソッドから構成されています。Point p1; でこのクラスを型とする変数を定義し、new Point(); で p1 に必要な記憶領域や情報を設定します。p1をクラス Pointの「オブジェクト」と呼びます
p1.px でPointクラスの変数(メンバー変数と呼ぶことがあります)pxを指定して値を設定できます。p1クラスのメソッドは p1.show()
で呼び出すことができます。show()メソッドは自分のクラスの座標(px,py)の位置に大きさ3の円を描きます。
Point p1;
void setup(){
p1 = new Point();
p1.px=30;
p1.py=20;
p1.show();
}
class Point {
int px;
int py;
void show(){
ellipse(px-1,py-1,3,3);
}
}
下は Point クラスにコンストラクタを追加した例です。new Point(引数)でオブジェクトを作成するとき、同じ引数の組を持つコンストラクタがあれば、その
new での引数を渡して対応するコンストラクタを呼び出します。この例では、受け取った値をクラス内部の変数 px,py に渡しています。
また、クラスにクラスの情報を文字列に変換する 「toString()」 メソッドを定義すると、print(オブジェクト) でクラスの情報を表示することができます。print でオブジェクトを指定すると、対応する
toString() を自動的に呼び出してくれます。
Point p1;
void setup(){
p1=new Point(20,20);
p1.show();
println(p1);
}
class Point {
int px;
int py;
//コンストラクタ
Point(int x,int y){
px=x;
py=y;
}
//描画
void show(){
ellipse(px-1,py-1,3,3);
}
//文字列表示
String toString(){
return "(x="+px+",y="+py+")";
}
}
///結果
(x=20,y=20)
実行画面
クラスを利用するとき、クラス内部の変数に直接アクセスをすることは「マナーが悪い」方法と言われます。クラスを定義する側では クラス内の変数を操作するメソッドを提供し、クラスを利用する側では、メソッドを通して変数を設定することが推奨されています。外部から変数を直接操作すると、クラス側ではそれをチェックできないからです。
-
クラスの継承
class 子クラス を定義するとき 「extends 親クラス名」 を付加することで、その親クラスを「継承」し 子クラス では親クラスの変数やメソッドをそのまま利用することができます。親クラスを「スーパークラス」、子クラスを「派生クラス」と呼びこともあります。
子クラスでは親クラスと同じメソッドを定義し直すことができます。この機能は親のメソッドを受け継ぎながら、一部のメソッドだけを自分なりに変更して利用したい場合有効な方法です。下の例では
musicianクラスで play() と sing() を定義しています。
Musician クラスを継承して Pianist クラスを作成します。Pianist クラスでは play() メソッドを再定義しています。setup()
で Musician と Pianist のオブジェクトを作成し メソッドを呼び出しています。同じ、m1.play() でも、m1 が Musician
のオブジェクトの場合と Pianistの場合とで実行されるメソッドが異なることに注意してください。
//継承
void setup(){
Musician m1 = new Musician();
m1.play();
Pianist p1=new Pianist();
m1=p1;
m1.sing();
m1.play();
}
class Musician{
void play(){println("play music");};
void sing(){println("sing a song");};
}
class Pianist extends Musician{
void play(){ println("play piano");}
}
///結果
play music
sing a song
play piano
クラスのメソッドは名前だけでなく、戻り値や引数の型でも区別されます。親クラスと同じメソッドを子クラスで定義しなおした場合、子クラスの内部では親クラスのメソッドでなく内部のメソッドが有効になります。
-
オブジェクトの配列とリスト
次のように、オブジェクトの配列 parray[] を作成することもできます。new Point(10*i+5,10*i+5)位置を変えながらオブジェクトを生成し、parray[]の要素にします。parray[i].show();で表示し確認します。
Point[] parray=new Point[13];
void setup(){
size(150,150);
for(int i=0;i<parray.length;i++)
parray[i]=new Point(10*i+5,10*i+5);//生成
for(int i=0;i<parray.length;i++)
parray[i].show();//表示
}
class Point {
int px;
int py;
//コンストラクター
Point(int x,int y){
px=x;
py=y;
}
//表示
void show(){
ellipse(px-1,py-1,3,3);
}
}
実行画面
-
オブジェクトのリスト構造
配列は生成時に数を指定する必要がありますが、事前に数が決まらない場合不便です。ArrayList は「リスト構造」と呼ばれ、不定個数のオブジェクトを記録可能な構造です。 add() で要素を追加でき、削除は番号を指定して
remove() で行います。
下のプログラムで、先頭の行で、ArrayList のオブジェクト olist を作成しています。mousePressed()でマウスボタンが押されたら、そのときのマウスの座標を
olist に追加します。表示は draw() でolist のすべての座標を表示していすす。olist.get(i) で olist の i 番目の要素を取り出しますが、olist
の要素は Point クラスではない(一般の object クラス)ので、(Point)olist.get(i) で、Point クラスであることを指定する必要があります。
ArrayList olist=new ArrayList();
void setup() {
olist.add(new Point(2, 3));
//olist.remove(0);
println(olist);
println(olist.get(0));
}
void draw() {
background(200);
for (int i=0;i<olist.size();i++) {
((Point)olist.get(i)).show();
}
}
void mousePressed() {
olist.add(new Point(mouseX, mouseY));
}
class Point {
int px;
int py;
Point(int x, int y) {
px=x;
py=y;
}
void show() {
ellipse(px-1, py-1, 3, 3);
}
}
実行画面
-
クラス内のクラス
クラスの内部でクラスを定義することもできます。内部のクラス(インナークラス)は外のクラスの変数やメソッドをそのまま利用できます。
//inner class
void setup() {
Outer outer = new Outer();
outer.field=1;
outer.foo();
Outer.Inner inner = outer.new Inner();
inner.bar();
}
public class Outer {
int field;
public void foo() {
Inner inner = new Inner();
inner.bar();
println("Outer:"+field);
}
public class Inner {
public void bar() {
int local = field;
// int local2 = staticField;
println("Inner:"+local);
}
}
}
///
Inner:1
Outer:1
Inner:1
Processing のプログラムはコンパイルすると 「PAppelet」クラス の内部クラスとして定義されるインナークラスになります。
Processing で既定義の名前(width や mouseX)は親の 「PAppele」 クラスで定義されている名前で、子クラス(Processing内部)で再定義すると、別の名前(メソッドや変数)として利用することになります。
-
static 修飾
クラス、変数、メソッド、の宣言の前に static を付加する場合があります。static が付加されると、(new で dynamic に生成する必要はなく)予め生成されていますから、クラス名.メソッド名
でメソッドの利用が可能です。また、static が付加された変数は new でクラスが動的に生成されても、複製されることなく共通の変数として利用できます。
void setup() {
Sc.sv=10;
Sc.sf();
}
static class Sc{
static int sv;
static void sf(){
println("sf:"+sv);
}
}
-
クラスのタブ記述
クラスは変数とメソッドが一体となるため独立性が高く、あるアプリで利用したクラスを別のアプリが再利用することが容易です。Processingでもクラスは別の「タブ」に記述すると便利です。タブを生成するにはツールバーの下で、右端
の右矢印をクリックします。New Tab のメニューが出ますからこれをクリックし、タブ名(通常クラス名)を指定し、OK します。これで、新規のタブが生成され、新しい編集ウインドウがでますから、ここにクラスのプログラムを書き込みます。タブの編集内容は、save
機能で同時にタブ名のファイルとして保存されます。また、プログラムを open するとタブも含めて展開されます。
下の例では Point クラスを別のタブに書き込んでいます。
-
まとめ
クラスを用いることで、プログラムの管理や再利用(クラスを別のアプリで利用する)することが容易になります。Processing でもほとんどの機能は
クラスの形式で提供されています。