1 オブジェクト指向

プログラム理論と言語 Part2-1
1.1
はじめにオブジェクトありき
まず処理において如何なるオブジェクトが必要かを考える
対象の分析・理解
オブジェクト指向
1
⇒ その理解を言語的に顕在化させるプロセス
操作対象の理解と言語の効率性
⇒ 「もの」の定義書としてのクラス
「もの」に付随したメソッド(演算、基本操作)群
関連したデータの塊を1つの「もの」として認識
⇒ メソッドを持つ「もの」として体系的に扱う
(1) クラス=「もの」と「もの」に付随した操作系
(2) クラスのことはクラス内部で処理
ブラックボックス化し、外部仕様を明確化
⇒ 部品としての再利用、安全性
(3) 「もの」と処理の独立性を高め処理も部品化
操作そのものでなく、操作の型情報のみで処理を記述
⇒ コードの抽象化、再利用
⇒ 役割・機能と「もの」を分離し独立性を高める
その後に、メソッドにより「もの」を操作・処理する手続き
オブジェクト指向言語=オブジェクト定義・操作言語
では、「もの」とは何か?
哲学論議はここではしない
計算機の中での出来事
⇒ メモリ上の構造物として表現・翻訳される「もの」
(4) 複雑さに対処するために階層として体系化
⇒ 修正・追加・利用(特にライブラリ、ユーティリティ)
人の対象理解・言語的表現と合致する
特に個別事例に依存しない抽象表現 ⇒ 言語の効率性
1
1.2
配列:プログラミングにおいて不可欠な「もの」
これも立派なオブジェクト
2
1.3
3角形は3つの点からなる: まず「点」を定義
定義は対応するクラスの記述で与えられる
class Point {
int[] a; a = new int[3];
データの集まり {a[0], a[1], a[2]} を1つの配列と
して 纏めて扱う。
new int[3] : 配列領域を割当・生成
配列は要素から構成され、
添字を与え要素へアクセス
特に a は領域全体の名前(参照)
領域を生成する仕掛け
一般のオブジェクト:
フィールド名を持つ要素からなり
添字の代わりにフィールド名(変数)で要素にアクセス
特定のデータ領域を占有し、
領域全体はオブジェクト参照変数で参照
領域生成は「コンストラクタ」で行う
//フィールド変数
double x, y, weight;
//コンストラクタ(生成方法)
Point(double x, double y,
double w) {
this.x=x; this.y=y;
weight=w;
}
//インスタンスメソッド
//各オブジェクトに直接作用
void show() {
System.out.println(
"x="+x+", y="+y+
" with weight "+weight);
}
}
フィールドアクセス:
this からは、フィールド名 x
一般に、a.x
Point a が参照している
オブジェクトの x フィールド
識別子が特定する位置の実体に対し
x のフィールド値をとりだす
Note: オブジェクトはCの構造体、参照変数は構造体型のポインタ変数
に対応する。メソッド(関数)と一体化して使う点がオブジェクト
指向を表しているが、上記の図ではその事情を十分説明できていな
い(インスタンスの図)。後で詳しく述べる。
3
4
1.4
Cの構造体
1.5
参照関係と内部状態の変化
オブジェクトと参照変数はCの構造体とそのポインタと似ている
似て(メモリ上の構造物という点は同じ)
非なるもの (使い方、その作法、安全性に対する配慮が全く異なる)
Cのポインタ:
Java の参照変数:
アドレス計算を許可
広義のポインタ
var, *var の2本立ての表記
しかし、アドレス計算を禁止
参照の定義と更新に限定
(表記の一本化)
struct student{
int stnum;
char stname[16];
};
...
struct student *stpt;
/* ポインタ */
...
stpt->stnum
(*stpt).stnum
class Student{
int stnum;
String stname;
}
...
Student st;
//参照変数
...
st.stnum
厳密表現 :Point の参照変数aが参照する(指す)オブジェクト
略した表現:Point a (単に、「点」a)
「仮の名前」
: 変数と実体の参照関係は動的に変化。内部状態も!
計算過程: 新規オブジェクトを生成しながら、
(1) 参照関係の変化、(2) オブジェクトの内部状態の更新
上記はメソッド実行時
stpt, st が参照する実体の stnum フィールドをゲット
stpt, *stpt を区別
参照変数は Student 実体、と考えてよい書き方
5
1.6
メソッド呼出とパラメータ
実体内部状態: メソッドにより更新され変化
実体の識別子: 何ら変わることはない(永続性 *)
⇒ 識別子(名前)と実体は同一視
6
1.7
つぎに、3角形を定義する
class Triangle {
//スタティック変数
static final int nofPoints=3;
//オブジェクトはフィールドで規定
Point[] points
= new Point[nofPoints];
//インスタンスメソッド
//オブジェクトに作用する関数
void show() {
for (short i=0;i<nofPoints;i++)
points[i].show();
}
}// 3角形に対する show() を
点に対する show() を使って定義
クラス情報 (型) で識別
基本データ型の引数: 値を引数パラメータに代入 (値呼出)
(*) 自オブジェクトを示す参照変数 this への代入(代入式の左辺)は禁
止で、コンパイルエラー
7
static final int nofPoints=3;
クラスに固有(インスタンスに依存しない)変数で
クラスのスタティックメンバーともいう。
final 指定変数は、上書き不可。ただし、下記の注意が必要となる:
1. 基本データ型: 文字通り、「定数」
2. オブジェクト: 参照先オブジェクトは変わらない。しかし、そ
の内部状態は変更される可能性
「定数」にするにはカプセル化が必要
8
1.8
コンストラクタ(オブジェクトの占有領域を作成)
1.9
定義・生成・操作
オブジェクトの定義をクラス記述で与え、
デフォルト生成:
Triangle t = new Triangle();
コンストラクタでオブジェクトを生成し、
インスタンスメソッドでオブジェクトを操作する
引数なしの特別な手続き
Point[] points
= new Point[nofPoints];
に従い実体を生成し t にセット
この場合、配列領域の確保(割当)
明示的な生成=デフォルト+α
Triangle t =
new Triangle(p0,p1,p2);
右辺実体 (の oid) を t にセット
Triangle( //引数付の手続き
Point p0, Point p1, Point p2) {
points[0]=p0; points[1]=p1;
points[2]=p2;
}
クラスのインスタンス化:
コンストラクタを適用し、実体オブジェクトを生成する行為
class triangletest {
public static void main(String args[]) {
Triangle t
= new Triangle(
new Point(1,2,3), //各点実体(の識別子)
new Point(4,5,6),
new Point(7,8,9)
); //右辺のオブジェクト (の識別子) を
//
左辺の変数(に代入)に参照させる
t.show(); // 3角形 t に show() を適用
}
// オブジェクトの操作は手続きで記述
}
// 「手続き的なオブジェクト定義・操作言語」
「論理型のオブジェクト指向言語 (OOLP)」、
「関数型のオブジェクト指向言語 (Common LISP)」
この他、「オブジェクト指向のデータベース」、
「オブジェクト指向の COBOL!」などもある。
扱うデータの基本単位としてオブジェクトを許すと、こうなる
デフォルトコンストラクタ:引数なしで Triangle() {}
クラス定義におけるデフォルトコンスタラクタの扱い
明示的コンストラクタを使用しない⇒ 宣言せずに使える
明示的コンスタラクタを使用
⇒ デフォルト Triangle() を使うときはそれも明示する
9
1.10
様々なメソッドの定義と組合せ:手続きを記述
class クラス 名 {
「もの」に対する 基本操作、または、複合操作
//インスタンスメソッド:
出力型 (クラス名または基本データ型)
// (基本操作を組合せた操作)
メソッド名 (パラメータ型 変数名, ...) {
メソッド本体(そのクラスのオブジェクト に対し行う処理
or オブジェクトがそのメソッドで振る舞う動作の記述)
class Point {
double x, y, weight;
double length(Point p) {// 2点の距離
return Math.sqrt(Math.pow(x-p.x,2)+Math.pow(y-p.y,2));
}// sqrt: 平方根、pow: べき乗。Math クラスで定義
...
}
class Triangle {
static final int nofPoints = 3; // 大域変数(定数)
Point[] points = new Point[nofPoints];
//
double sumOfLength() {// 3つの辺長の総和
double sumOfLength = 0.0;
for (int i=0;i<nofPoints;i++) // i++ は i=i+1 の略記
sumOfLength += points[i].length(points[(i+1)%nofPoints]);
return sumOfLength;
}
// 整数 n, m に対し、n % m は m を法とした剰余
public static void main(String args[]) {
Triangle t = new Triangle(
new Point(1,2,3), new Point(4,5,6), new Point(7,8,9)
);
if (t.checkTriangle())
System.out.println("辺の長さの総和 = "+ t.sumOfLength());
else {
System.out.println("3角形ではありません。要データチェック");
t.show();
};
}
}
11
10
1.11
用語の整理1: インスタンスとオブジェクト
オブジェクト: 操作の「対象」で、占有領域 (インスタンス) と
それを処理するメソッドを持つ
Cでも構造体のインスタンス化は行う。しかし、そのインスタンスは関
数(メソッド)と言語的に一体化しているわけではない。プログラ
マが必要に応じて、そのインスタンスへのポインタを関数に渡して
適用・操作するだけの話
一方、Java では、最初からインスタンスはメソッドと一体化されて扱わ
れることが大前提であり、そのことがクラス定義で明示される。そ
れは体系的に操作対象を扱うための流儀であると理解すること。
12
1.12
操作により「もの」をクラス分けする
オブジェクトがどのようなメソッドを使用できるかはクラス
定義で決まる。逆に言えば、各オブジェクト毎にメソッ
1.13
カプセル化
クラスの利用、クラス間相互作用は一般に複雑化
外部仕様: クラスとは? ⇔ ○○のメソッドを持つ「もの」
ドを定義し保持するのは不経済
オブジェクト毎に異なるメソッドが必要な場合:
別クラスの「もの」 ⇒ メソッドがクラスを特徴づける
上図における「人」は識別子と考えてもよい。以後、概念図において「人」
記号は、識別子もしくはインスタンス、とする
~ メソッド中心の設計
仕様と実装の分離独立化
仕様に関わるメソッドのみ公開
内部的な実装は private に
外部からは公開メソッドのみで操作
隠蔽し独立に管理
オブジェクト指向はさらなる構造化を促す:
独立したクラスの列挙から、構造化されたクラス群
1. クラス階層、インタフェース階層
2. 内部クラス(一般には クラスの nesting)
13
1.14
アクセス制御の原則
14
1.15
フィールドは原則 private に
(不用意なアクセスによる状態参照・更新を抑制する)
ゲッター getAttr() 、+セッター setAttr(...)
メソッドに対するアクセス制御
同一パッケージ(同一ディレクトリ)を想定した場合:
無修飾のメソッド: パッケージ内で公開
private メソッド: クラス内のみで有効
一般には、パッケージの作り方に依存 (特に、public)
用語の整理2: obj.m(p1,.., pn) の読み方
『オブジェクト obj に対し、
メソッド m(p1,.., pn) を 適用』
『obj に対し、m(p1,.., pn) を 実行』
『オブジェクト obj に対し、
メソッド m(p1,..,pn) を 呼び出す』
メソッドはオブジェクトの所有物
本講義では、特に区別しない
この他、オブジェクトをエージェント(処理主体)と考え、
『オブジェクトにメッセージを 送る』
複数のパッケージ(複数のディレクトリ)の場合:
public: 全てに公開。protected, final: クラス階層導入時
むやみに意味もなく public をつけない!
パッケージ配布・公開の際の外部仕様
ソースコード解読時の構文上の情報
インタフェースの定数とメソッドは must be public
protected: パッケージ内+下位クラスに公開
final 定数・メソッド:上書きを許さない
クラスに対しても、public, private, final、無修飾等の修飾が可能.
例えば、public にしないと他パッケージから利用できない
final にすると、継承・拡大を許可しない
15
16
1.16
メッセージパッシング
1.17
『オブジェクト obj (レシーバ)に、
メッセージ m をパラメータ p1,.., pn で送る』
Definition of A: A consists of B 1, B 2,...
オブジェクト指向: 言語的な定義のスタイルに対応
Aの定義書: 『クラスAのオブジェクトは
送られた方は m を処理するメソッドを持つ
クラス(or 基本型)B j のフィールドからなり
メッセージ ~ メソッド
○○のメソッドを使って操作します』
(構成要素(part of, has_a 関係)
class Teacher {
...
void assignRept(Student st,
Task task) {
Answer ans = st.report(task);
// st に「report せよ」との
//
メッセージを発信し、
//
結果を ans で受ける
if (ok(ans)) {...}
else {...};
...
}
class Student {
//メッセージを受けたときの
// 動作 ~ メソッドそのもの
Answer report(Task task) {
...
Answer myAnswer
= new Answer(...);
return myAnswer;
// 解答を myAnswer で返す
}
}
クラスのメンバー
A isa B, or A is defined as a B such that ...
A extends B
... 「AはBを継承し拡大」
Aに固有なフィールドやメソッドを追加
A has a role/function of B
B: インタフェース
A implements B
機能を持つ ~ 役割・機能を実現する手段・メソッドを
具体的に持つ(実装)
⇒ 多態性(ポリモルフィズム)
⇒ 様々な役割や機能を実装クラスで記述
(クラスと機能の分離・独立化を促進)
実際の例題は、FAのシミュレーション (statisfa.java, fa.java) で示す
17
1.18
クラス定義のまとめ
18
1.19
class Point {
実行時: クラスは
private double x,y;
生成されたオブジェクトの集まり +
Point(double x, double y) {
大域変数(static variable)の値
this.x = x; this.y =y;
を管理
}
void show() {
コンパイル時: オブジェクトの定義書として記述
System.out.println(" x="+x
の集まり。記述は、クラスのメンバー(構成
+", y="+y);
要素)と呼ばれる変数やメソッドの各々に対
}
し行われる
}
フィールド変数: オブジェクトの属性
class Triangle {
基本データ型もしくはオブジェクト参照変数
private static final
フィールド記述がコンスラクタの
int nofPoints=3;
デフォルト動作を決める
private Point[] points
= new Point[nofPoints];
インスタンスメソッド:
private Triangle(Point p0,
オブジェクトに直接作用する手続き
Point p1, Point p2) {
オブジェクト: this で表記(必要ならば)
points[0]=p0; points[1]=p1;
points[2]=p2;
スタティック (static) メンバー
}
スタティック変数:クラスが保持し、
private void show() {
各オブジェクトに依存しない変数
for (short i=0;i<nofPoints;i++)
スタティックメソッド:
points[i].show();
オブジェクトに依存しない手続き
}
public
static void main(String args[])
public static
java クラス名 引数
void main(String args[]) {
そのクラスの main が実行される。
Triangle t
クラス毎につけてよい
= new Triangle(
型は void main(String[])。引数型を別に
new Point(1,2),
すると「オーバーロード」
new Point(4,5),
new Point(7,8)
左記では main は Triangle のメンバー
);
別クラスで main を動作させる場合
t.show();
Triangle(...) と void show() の private 指定
}
//void main(String[]) は
は外す(メソッドにアクセスできないから)
} //ここでは Triangle に組み込んだ
19
スタティックメンバーについて
オブジェクト指向では、先んずオブジェクトとクラス設計
その次に手続き等の設計
個々のオブジェクトに依存しない定数・変数・メソッドがあ
れば、スタティックメンバーにする
具体にスタティックメンバーが必要となる場面例:
コンストラクタに準じた作用を持つメソッド
クラス固有の定数やオブジェクト(static final)
個々の「もの」に依存しないクラス固有の処理手続き
基本データ型に関する型変換や数値関数など
「ラップクラス」や Math で提供される
int Integer.parseInt(String),
double Math.sqrt(double)
20
2
インタフェース
2.1
Java インタフェース: クラスとしての解釈 (*):
『指定された型のメソッド群を持つ「もの」』
『動物に鳴けと言えば、猫なら「ニャン」と鳴
き、犬なら「ワン」と鳴く(吼える?)』
インタフェースのメソッド:
メソッド名と型情報のみの「抽象メソッド」
1. 「鳴け」というコマンド自体は 抽象的
2. 対象指示物(犬や猫)を具体に与えた上で、
「鳴け」と命令
3. 対象物は「鳴け」を 具体化した動作 を持ち
それを実行する
実装クラスでの実装(具体化)の義務
インタフェースの利用: 「もの」そのものでなく、
「もの」が持つ操作の名前と型のみで処理を記述
⇒ 処理コードの抽象化 ⇒ 汎用で再利用度が高い
⇒ 多態性:同一の言葉・表現だが様々な振る舞い
多態性のお話風の例示について
いくつかの教科書に掲載されている多態性の喩
インタフェースを用いた多態性:
「実装クラス」におけるメソッド定義に依存
1. メソッド自体は名前と型情報のみ
抽象メソッド
2. メソッド適用時: 操作の対象物 が与えられ、
3. 対象物が所有する
名前と型が同一な具体的なメソッド が起動
役割・機能の部品化:
特定の役割・機能を実装クラスに。呼び出して使う
(*) インタフェースは言語仕様上はクラスと区別されるが、コンパイル
した後の実行時の扱いはクラスと同じ。実際、コンパイル後のイン
タフェースの拡張子はクラスと同じ .class
実際の Java インタフェースは、抽象メソッド以外にも、
定数(public static final)を持てる。
抽象メソッドと定数が一体化している場合は、一緒に書いてよいが、
そうでない場合は区別して独立したインタフェースにする
型: 外形的な仕様
メソッド名、パラメータ(引数)型、出力型
喩え:
『筐体の中身・動作は異なるが、プラグやコネクタ
が同じなら「型」は一致』
21
2.2
インタフェース例
22
2.3
interface HasSalary {// 指定された public メソッド を持つ「もの」
int salary(); // インタフェースで宣言されたメソッドは、public
}
class PTEmp
implements HasSalary {//インタフェースの実装クラス
//
その実体は HasSalary 参照変数で、参照可
private int hoursVage, hoursPerWeek;
PTEmp(int hoursVage, int hoursPerWeek) {
this.hoursVage = hoursVage; this.hoursPerWeek = hoursPerWeek;
}
public int salary() { // 実装
// (型の同一性で、どのメソッドの実装かを識別)
return hoursPerWeek * hoursVage * 4; // 4 weeks
}
}
class FTEmp implements HasSalary {
//
private int monthlySalary;
FTEmp(int monthlySalary) {
this.monthlySalary = monthlySalary;
}
public int salary() {return monthlySalary;} // 実装
}
class Company {
private HasSalary[] emps; // インタフェース実装クラス実体を
int sumOfSalary() {
// 要素に持つ配列
int sum = 0;
for (int i=0;i<emps.length;i++)
sum += emps[i].salary();
// emps[i] が持つ salary() を実行
用語の整理: メソッドの型について
class ζ {
...
σ method (τ1 x1 , ..., τn xn ) { ... }
...
}
一般に、シグネチャ:記号のシステムで
型 : ζ, , σj , ...
定数 : a : ζ, ...
関数 : f : ζ, τ1 , ..., τn ⇒ σ
本講義:メソッド名・引数と出力型情報を、単に型と呼ぶ
σ method (τ1 , ..., τn )
Java:シグネチャとは、メソッド名と引数型 method (τ1 , ..., τn )
1. シグネチャでオーバロードを決定する
2. オーバーライドの場合は、型が同一なものがない場
合、コンパイルエラー
return sum;
}
Company(HasSalary[] emps) {this.emps = emps;}
public static void main(String args[]) {
HasSalary[]
emps = {new PTEmp(10,100), new FTEmp(2000)};
Company c = new Company(emps);
System.out.println(c.sumOfSalary());
}
}
23
(ζ : self, this の型 )
型はさらに階層化(部分型)~ クラス階層
24
2.4
まとめ: 実体を与え、実行時にメソッドが決まる
2.5
多重継承に関するコメント: 階層化への導入
一部の教科書・Web ページには
『インタフェースはクラス階層での多重継承問題を解決する』とある
インタフェース階層では、メソッドの型情報を継承(集める)
クラス階層では、定義・実装を継承し集める
同じ「継承」だが、集める対象が異なり意味が違う
階層種別
継承し集める対象
意味
インタフェース階層 メソッドの型情報 役割・機能の型を宣言
定義済みの動作を複数の役割で使う
オーバーライド: 上位クラスに型が同一のメソッドがもしあ
れば、下位クラスの同一型のメソッドで「上書き」(*)
実行時に実体オブジェクトに対して決まる
クラス階層
メソッドの定義
動作を定義する
型が同じ複数のメソッドを定義すると困る
つまり、定義の問題と使い方の問題は、問題として違うという話
インタフェースと実装クラスの関係は、クラス階層における
「上位・下位」とは異なるが、オーバライドに関しては
全く同じ (*)
継承問題の扱いはオブジェクト指向言語によっても、扱いが異なる。各
言語の仕様を確認したうえで使うこと
本格的・体系的分類が必要でない場合、クラス階層利用は限定的
実際の Java ではクラス階層の中にインタフェースを組み込んだ「抽象
クラス」があり、話がさらに紛らわしい
本講義では:
⇒ 抽象クラスは単にインスタンス化できないクラス
⇒ 抽象クラスの抽象メソッドは、単に便宜を図るため
(本当は全てインタフェースとしてきちんと書くとの立場)
25
26