プログラム理論と言語 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
© Copyright 2024 Paperzz