Comments
Description
Transcript
インタフェース 多態性のお話風の例示について 少しは実用的な例
プログラム理論と言語 Part2-1-3 インターフェイス Java インターフェイス: クラスとしての解釈 (*): 『指定された型のメソッド群を持つ「もの」』 インターフェイスのメソッド: メソッド名と型情報のみの「抽象メソッド」 実装クラスでの実装(具体化)の義務 インターフェイスの利用: 「もの」そのものでなく、 「もの」が持つ操作の名前と型のみで処理を記述 ⇒ 処理コードの抽象化 ⇒ 汎用で再利用度が高い ⇒ 多態性:同一の言葉・表現だが様々な振る舞い 「実装クラス」におけるメソッド定義に依存 役割・機能の部品化: 特定の役割・機能を実装クラスに。呼び出して使う (*) インターフェイスは言語仕様上はクラスと区別されるが、コンパイルした後の実行時の扱いはクラ スと同じ。実際、コンパイル後のインターフェイスの拡張子はクラスと同じ .class 実際の Java インターフェイスは、抽象メソッド以外にも、 定数(public static final)を持てる。 抽象メソッドと定数が一体化している場合は、一緒に書いてよいが、そうでない場合は区別して 独立したインターフェイスにする 1 オブジェクト: メソッドを持つもの class IntCell { private int value ; int getValue() {return value;} private IntCell next; IntCell next() {return next;} IntCell(int value) {this.value = value;} IntCell(int value, IntCell cell) { this.value = value; next = cell; } void showValue() {System.out.print(value+" ");} } class IntList { private int size = 0; private IntCell firstCell = null; void tail() {// firstCell = firstCell.next(); size--; } /* IntList オブジェクトはフィールドとして IntCell オブジェクトを参照. IntCell で公開されているメソッド(やフィールド)しか使えない. カプセル化されたクラスのオブジェクトは他クラスからは『メソッドを持つもの』 他クラスからはメソッドの定義(実装)は隠されブラックボックス so, 実装を変えれば,もちろん,動作は異なる クラスごとに実装を変えるが,実装クラスに依存しないコードを書く ⇒ 多態性(polymorphism) */ 2 多態性のお話風の例示について いくつかの教科書に掲載されている多態性の喩 『動物に鳴けと言えば、猫なら「ニャン」と鳴 き、犬なら「ワン」と鳴く(吼える?)』 1. 「鳴け」というコマンド自体は 抽象的 2. 対象指示物(犬や猫)を具体に与えた上で、 「鳴け」と命令 3. 対象物は「鳴け」を 具体化した動作 を持ち それを実行する インターフェイスを用いた多態性: 1. メソッド自体は名前と型情報のみ(定義も持たない) 抽象メソッド 2. メソッド適用時: 操作の対象物 が与えられ、 3. 対象物が所有する 名前と型が同一な具体的なメソッド が起動 型: 外形的な仕様 メソッド名、パラメータ(引数)型、出力型 喩え: 『筐体の中身・動作は異なるが、プラグやコネクタが同じなら「型」は一致』 3 少しは実用的な例: ヒープソートで例示 ソーティングのプログラム: 対象が持つ大小の比較情報のみに依存 Therefore, 比較操作のみインターフェイスにしておけば、任 意のソーティングプログラムは対象クラスと独立に書け る ヒープソートは、固定配列で2分木を単純に操作 配列インデックスの算術計算でOK 新規要素は配列の最後に挿入(バランスがとれた2分木) 要素の削除: 最後の要素と置き換える parent = (child-1)/2 (整数の割り算) 関連ユーティリティ java.util には、集合やソーティング関係のインターフェイス、クラスが提供さ れている。調べてみること。 4 ヒープソートの動作 Heap: 高さ O(log n) の2分木で, 制約 「親の値は子の値より小さい 」を満たす 5 コーディング 先んず、大小比較のためのインターフェイス 【メソッドの型 = 出力型 メソッド名(引数型)】(*) interface ObjectWithLessThan { boolean lessThan(ObjectWithLessThan y); void show(); } // 「lessThan メソッドを持つもの」 // 「lessThan の操作対象となるもの」 // 自動で public 指定(全てに公開) 既に作成済みのプログラムを「抽象化」 「int x」 を 「ObjectWithLessThan x」 に 「(x < y)」 等の比較判定は「x.lessThan(y)」等に もちろん、実装クラスは完全に無視し、型情報 boolean lessThan(ObjectWithLessThan) のみを想定して”抽象的に”コーディングしても良い (*) (Java の) シグネチャ: メソッド名+引数型 オーバーロードのときの条件を与える オーバーライドの際は、メソッドの型=シグネチャ+出力型 一般に、引数型と出力型の組をシグネチャと定義することも多い 本講義: 型情報と名前を合わせて単に「型」と呼ぶ 6 実装クラス: 具体に処理対象となるクラス class ClassName implements Interface1,Interface2, ...(複数可) 実装の義務: implements する全ての抽象メソッドに対し 同じ型の public メソッド(実装メソッド)を持つこと 実装としての使用を 型の同一性で暗に識別 「実装」という、特別なメソッド定義法があるわけではない 定義済みの一部のメソッドを抽象メソッドの具体化として識別 class Student implements ObjectWithLessThan{ private String name; private int score, code; public boolean lessThan(ObjectWithLessThan y) {// 型で識別 Student yrec = (Student)y; // 要キャスト /* y は objectWithLessThan の参照変数ゆえに, y が Student オブジェクトを参照している場合でも,y.name() はバツ キャストして yrec.name() 等,Student のメソッドを使えるようにする */ return name.compareTo(yrec.name) < 0; // return code < yrec.code; //実装を変えて別の順序も可 // return score > yrec.score; // score の降順 } public void show() { System.out.print("【 "+name+","+score+"】 "); } } 7 用語の整理: メソッドの型について class ζ { ... σ method (τ1 x1 , ..., τn xn ) { ... } ... } 一般に、シグネチャ:記号のシステムで 型 : ζ, , σj , ...(クラス,インターフェイス,基本データ型) 関数 : f : ζ, τ1 , ..., τn ⇒ σ (ζ : self, this の型 ) 本講義:メソッド名・引数と出力型情報を、単に型と呼ぶ σ method (τ1 , ..., τn ) Java:シグネチャとは、メソッド名と引数型 τ method (τ1 , ..., τn ) 1. シグネチャで オーバロード を決定 型が異なるメソッドは名前が同じでも別のメソッド 2. インターフェイスのメソッドと実装クラスにおける 実装は、オーバーライド(上書き) 型が同一なものがない場合、コンパイルエラー 8 動的ディスパッチ,キャスト 明示的キャスト: Interface z; z = (C)x; 上位の変数は下位のオブジェクトを参照可.逆は× インターフェイス参照変数は,実装クラスオブジェクトを指す x が C のインスタンスを指していれば成功 ow. 失敗(実行エラー: 『キャストはできません』) クラス階層の場合も全く同じ(後述) 基本データ型の場合は, 「型縮小」 double z = 1.0; int x = (int)z; 9 ソースコード例 class Heap { private ObjectWithLessThan[] heap; ObjectWithLessThan get(int index) {return heap[index];} private int size; int size() {return size;} private int capacity; Heap(int capacity) { heap = new ObjectWithLessThan[capacity]; size = 0; this.capacity=capacity; } private void swap(int pt1, int pt2) { ObjectWithLessThan tmp; tmp=heap[pt1]; heap[pt1]=heap[pt2];heap[pt2]=tmp; } private boolean nonRoot(int pt) {return pt > 0;} private int parent(int pt) {return (pt-1)/2;} private int succ(int pt) {return 2*pt+1;} // left successor (child) // right は succ+1 void insert(ObjectWithLessThan element){ if (++size > capacity) { System.out.println(" Heap: sorry, no space any more"); return; }; int pt, parent; heap[(pt=size-1)]=element; while(nonRoot(pt) && heap[pt].lessThan(heap[(parent=parent(pt))])) { swap(pt,parent); pt = parent; }; } private ObjectWithLessThan deleteMin(){ ObjectWithLessThan min = heap[0]; heap[0] = heap[--size]; int pt = 0, branch; while((branch=succ(pt)) < size) { if (branch+1 < size && heap[branch+1].lessThan(heap[branch])) branch = branch+1; //take min among left and right children if (!heap[branch].lessThan(heap[pt])) break; swap(branch,pt); pt = branch; }; return min; } void showInTheOrder() { while (size > 0) deleteMin().show(); System.out.println(); } 10 interface ObjectWithLessThan { boolean lessThan(ObjectWithLessThan y); // インターフェースメソッドは他クラスで使用されることが大前提 // よって, public と理解しておく void show(); } class Student implements ObjectWithLessThan{ private String name; private int score; Student(String name, int score) {this.name=name; this.score=score;} // public boolean lessThan(ObjectWithLessThan y) { return score < ((Student)y).score; //成績昇順 // Student オブジェクトに対し lessThan を呼び出すときの引数は Student // so, Student にキャストし,score フィールドにアクセス // y が Student でないオブジェクトを参照している場合は, 「キャストエラー」 } public void show() { System.out.print("【 "+name+","+score+"】 "); } public static void main(String args[]){ Student[] records = { new Student("mh",10), new Student("keiko",50), new Student("kenichi",100), new Student("taro",70) }; Heap heap = new Heap(records.length); for (int i=0; i<records.length; i++) heap.insert(records[i]); heap.showInTheOrder(); } } 11 実装クラス → ← インターフェイス は多対多 インタフェースは複数の実装クラス を持ち,クラスは複数のインタ フェースを実装できる. C1 オ ブ ジェク ト は ,メ ソッド {m11, m12} を持つもの(イン タフェース os1)であり,かつ メソッド {m21, m22} を持つも の(インタフェース os2)でも ある. 「ある機能を持つ∼ある種のことができる∼対応するメソッドを持つ」 インタフェースの実装クラスは,機能を実現しているとも理解できる インタフェースと実装の多対多性: クラスは複数の機能を実現 12 多重継承に関するコメント: 階層化への導入 一部の教科書・Web ページには 『インターフェイスはクラス階層での多重継承問題を解決する』とある インターフェイス階層では、メソッドの型情報を継承(集める) クラス階層では、定義・実装を継承し集める 同じ「継承」だが、集める対象が異なり意味が違う 階層種別 継承し集める対象 意味 インターフェイス階層 メソッドの型情報 役割・機能の型を宣言 定義済みの動作を複数の役割で使う クラス階層 メソッドの定義 動作を定義する メソッド定義を継承すべき親は唯一 継承問題の扱いはオブジェクト指向言語によっても、扱いが異なる。各言語の仕様を確認したうえで使 うこと 本格的・体系的分類が必要でない場合、クラス階層利用は限定的 実際の Java ではクラス階層の中にインターフェイスを組み込んだ「抽象クラス」があり、話がさらに 紛らわしい 本講義では: ⇒ 抽象クラスは単にインスタンス化できないクラス ⇒ 抽象クラスの抽象メソッドは、単に便宜を図るため (本当は全てインターフェイスとしてきちんと書くとの立場) 13 本日の課題1 下記プログラムは、凸多角形(任意の頂点を結ぶ線分が多角形の内部もし くは外周上にある)のクラス ConvexPolygon において、外周の長さを求める (double boudaryLength())。プログラムの動作を説明しなさい(ヒープソー トを使っているので、当然、何かをソートしている)。ただし、ヒープから要素 を順次読込むために、 (public) ObjectWithLessThan read() { if (size > 0) return deleteMin(); else return null; } を Heap クラスのメンバーとして追加している。なお、点や多角形のデータ読 み込みと、その方法に依存するコンストラクタは省略している。 また、オブジェクト指向の考え方からは不適切なコードを下記は含んでいる(効 率上は下記でOKだが ...)。それがどこかを指摘しなぜ不適切かを論じなさな い。解決策を提案できればなお良い。 class Point implements ObjectWithLessThan { private static final double PRECISION = 1.0e-7; // 精度定数 PRECISION: 浮動小数点実数の等号、不等号判定に用いる private double x, y; double distance(Point p) {//pow(x,y)=x^y (べき乗), sqrt は平方根 return Math.sqrt(Math.pow(y-p.y,2)+Math.pow(x-p.x,2)); } private static Point standardPoint; static void setStandardPoint(Point p) { standardPoint=p; } boolean eqX(Point p) {// X 座標が等しい return Math.abs(x - p.x) < PRECISION; } public boolean lessThan(ObjectWithLessThan ap) { Point p = (Point)ap; if (eqX(standardPoint)) if (standardPoint.eqX(p)) return y + PRECISION < p.y; else return false; if (standardPoint.eqX(p)) return true; return slope(standardPoint) > p.slope(standardPoint) + PRECISION ; } boolean pointEQ(Point p) {// 点の同一性 return Math.abs(x-p.x) < PRECISION && Math.abs(y-p.y) < PRECISION ; } public void show() {System.out.println("x="+x+", y="+y);} 14 private double slope(Point p) { return (y-p.y)/(x-p.x); } /* (y-p.y)/(x-p.x): 0による除算 ⇒ 非数値定数 NaN, infinity このプログラムでは slope は Point の private メンバー その範囲 (Point クラス) で調べて、lessThan だけが使用 lessThan で分母が0になる場合は除去されている. カプセル化がデバッグの手がかりになる一例 */ boolean moreLeftHigher(Point p) { if ( x + PRECISION < p.x || (eqX(p) && p.y + PRECISION < y) ) return true; else return false; } } class ConvexPolygon { private int nofPoints; // 点の数 private Point[] points; //点の配列 private Point standardPoint; private void setStandardPoint() { standardPoint = points[0]; for (int i=1;i<nofPoints;i++) if (points[i].moreLeftHigher(standardPoint)) standardPoint = points[i]; Point.setStandardPoint(standardPoint); } private double boundaryLength() { Heap heap = new Heap(nofPoints-1); //standardPoint 以外の点のヒープを形成 for (int i=0;i<nofPoints;i++) if (!points[i].pointEQ(standardPoint)) heap.insert(points[i]); double boundaryLength = 0.0; Point presentPoint = standardPoint, nextPoint; while ((nextPoint = (Point)heap.read())!=null) { boundaryLength += presentPoint.distance(nextPoint); presentPoint = nextPoint; }; return boundaryLength + presentPoint.distance(standardPoint); } public static void main(String args[]) { ConvexPolygon cp = new ConvexPolygon(); cp.readDataFromFile(args[0]); // 点の読込プロセスは省略 // readDataFromFile 実行後、points と nofPoints が定義されたとする cp.setStandardPoint(); System.out.println("外周長:"+cp.boundaryLength()); } } 15 参考(凸多角形と凸包形成) 凸なことが既知の場合の処理を 考えた 凸でない場合の処理、例えば、 点集合に対し凸包(点集合 を内部に含む最小の凸多角 形)を求めるアルゴリズム 例示したプログラムの簡単な拡 張で済む: 1. 順序 lessThan の定義を修正 2. その順序に従い、基準点以外の点を列挙しながら、 凹になる候補点を除去する 凸包を求めるアルゴリズムは応用上も重要 (数理計画法、最適化問題) 膨大な点集合に対する最適解を上包(下包) 上の点に限定して算出 (目的関数の凸性も必要だが ...) (Graham の方法、Jarvis の方法、Quickhull 法など) 16