34
1 「認知システム論」の授業では,人工知能(AI)のさまざまなアルゴリズムを学ぶことに なるが,可能ならばそれを実際のプログラミング言語で実装してみることが大事である. しかし,この授業では,時間の関係でそこまで扱うことができないので,興味のある人が 将来,自立的に実装できるように,最小限のプログラミングの知識を今回の授業で学ぶ. 具体的には,AIプログラミング言語の歴史の概略を学んだ後,オブジェクト指向プ ログラミングの考え方とその3大特徴およびJava言語によるプログラムの記述例を学 ぶ.JavaはAIプログラミング言語というわけではないが,過去のAIプログラミング言語の 研究成果の影響を受けて設計されており,インターネットなどと連携した現代的なAIプ ログラムを記述するのに適している.

Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

1

「認知システム論」の授業では,人工知能(AI)のさまざまなアルゴリズムを学ぶことになるが,可能ならばそれを実際のプログラミング言語で実装してみることが大事である.しかし,この授業では,時間の関係でそこまで扱うことができないので,興味のある人が将来,自立的に実装できるように,最小限のプログラミングの知識を今回の授業で学ぶ.

具体的には,AIプログラミング言語の歴史の概略を学んだ後,オブジェクト指向プログラミングの考え方とその3大特徴およびJava言語によるプログラムの記述例を学ぶ.JavaはAIプログラミング言語というわけではないが,過去のAIプログラミング言語の

研究成果の影響を受けて設計されており,インターネットなどと連携した現代的なAIプログラムを記述するのに適している.

Page 2: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

2

人工知能の歴史の中で,初期のころ(1960年代以降)は,AIプログラミングといえば,それ専用のプログラミング言語を使うものと相場が決まっていた.それは基本的にはLISP というリスト処理言語だった.ゴミ集め(garbage collection)という革新的なメモリ

管理機能は,不要になった「ゴミ」のデータを再利用して「環境」を整備するのに効果的だ.LISPは関数型言語と呼ばれる種類の言語であり,プログラムは関数の集まりとして定義され,関数は他のデータと同格の「第1級」のオブジェクトとして扱われるため,関数

の引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を関数からの戻り値とすることもできる.

1980年代になると論理型言語である Prolog もAIプログラミング言語の仲間に加わった.

論理学における「述語」と呼ばれるものが,手続きや関数のような基本的なプログラム部品として使われる.述語のパラメータ(仮引数)とアーギュメント(実引数)は,パターン照合によって比較され,両者が一致するように適切な「代入」が実行される.また,実行すべき処理に「非決定的」な選択肢が含まれており,深さ優先探索(バックトラック)によって適切な選択肢を自動的に探索する機能がある.

その後,プログラミング言語の発展もあり,AIの研究テーマも多岐にわたってきたことから,最近では,通常の言語でも十分,AIプログラムを書けるようになってきた.たとえばJava の登場である.Javaの特徴は,オブジェクト指向による再利用性の高いプログ

ラミングができること.インターネット時代を反映して,ネットワークプログラミングが容易にできること.グラフィカル・ユーザ・インタフェース(GUI)のプログラミング機能が充実していることなどである.しかし,最大の特徴は Write-Once Run-Anywhere (一度書

けば,どこででも走る)というキャッチフレーズで表現される可搬性(ポータビリティ),あるいは事実上すべてのOSで動作して,しかも同じ動作をすることが保証されるプラット

フォーム非依存性と呼ばれる性質によって,一度作ったプログラムの実行コードを再コンパイルなしに事実上,ネットワーク上のどこででもただちに実行できることである.

Page 3: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

3

今回の授業では,オブジェクト指向プログラミングの最も特徴的な考え方を学ぶ.

具体的なプログラミングはJavaを用いるが,Javaの詳細を学ぶことが目的ではない.

Part1でオブジェクト指向の基本概念を説明した後,Part2でオブジェクト指向の3大特徴を学ぶ.

Page 4: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

4

Page 5: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

5

オブジェクト(object)とは何か?

その答えを短く言うと,「データと操作をカプセル化した「もの」」となる.

とはいえ,それで内容が理解できるとは思えないので,順を追って内容を見ていく.

この授業では,AI的な例題として,このロボットを動かすプログラムを考える.このロボットが「オブジェクト」のつもりである.

このロボットはx軸上を進むものである.

ロボットの状態は,現在の「位置」,現在の「速度」,現在の「燃料」の3つのデータで表すことができる.それらのデータはロボットの胸のあたりにあるメモリーに内蔵されている.

また,このロボットはリモコンの3つの操作を受け付ける.

1つめの操作は,「どこ?」というボタンを押すことで,現在位置(x座標の値)を返してくれる.(それは,たとえば,リモコンの表示画面に表示される.)

2つめの操作は,「変速」というボタンを押して,速度を設定できる.実際には,このボタンを押すほかに,さらに設定すべき速度の値を補助情報として入力する必要がある.

3つめの操作は,「進め」ボタンで,これを押すと,1秒間だけ,現在の設定速度でロボットが前進する.

リモコンからのこれらの指示を実行するためのプログラムがロボットの足のあたりに内蔵されている.

オブジェクト指向プログラミングの用語では,いま述べた「データ」のことを「フィールド」(field),「操作」のことを「メソッド」(method)という.「フィールド」と「メソッド」を総称して「メンバ」(member)ということもある.

一般的なプログラミング用語で言えば,フィールドは変数,メソッドは関数のようなもので,いずれもデジタルなデータあるいは機械語コードで表現できる.それらが,このロボットという「オブジェクト」の中にまとめて閉じこめられている様子を「カプセル化」(encapsulation)というのだが,もっと正確な説明は後にわかってくる.

重要なのは,「オブジェクト」というのは単なる概念ではなく,これらのデジタルなデータまたはコードからなり,コンピュータのメモリの一定領域を占める具体的な「実体」だということである.この「実体」あるいは「もの」が英語のobjectという単語の意味である.

Page 6: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

6

「オブジェクト」は一定の実メモリ領域を占める「もの」であるが,ふつう,オブジェクト指向でプログラムを書くと,そのようなオブジェクトがたくさん生成される.それぞれのオブジェクトごとにメモリ領域が占められる.

ロボットの場合には,このスライドのように,状態(各フィールド「位置」,「速度」,「燃料」のそれぞれの値)の異なるロボットが生成されるわけである.

ただし,操作(メソッド)を表すプログラムコードはこれらのオブジェクトに共通である.

Page 7: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

7

オブジェクトにはいろいろな種類のものがあってよい.

たとえば,オブジェクト指向で書かれたあるコンピュータゲームの中では,ロボットのほかに船が出てきて,ロボットが船に乗って,いろいろな島に渡って冒険をするのかもしれない.この場合,「ロボット」と「船」は異なる種類のオブジェクトである.

オブジェクトの種類のことを「クラス」(class)という.この例の場合,ロボットクラス,船クラスという2つのクラスのオブジェクトが存在することになる.

Page 8: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

8

オブジェクト指向プログラミングで初心者が最初につまづく点は,「クラス」と「インスタンス」の区別である.

クラス(class)とは,オブジェクトを作るための「ひな形」あるいは「設計図」のようなもの

である.ロボットの例の場合,「位置」,「速度」,「燃料」というフィールドがあることがこの設計図に書かれている.しかし,当然だが,その「現在の値」というのはない.

一方,インスタンス(instance)とは,この設計図から作られた「実例」のことであり,各

フィールドには「現在の値」が記録されている.このスライドでは,1つのクラスから異なる3つのインスタンスが生成されている.これまで出てきた「オブジェクト」という言葉は,この「インスタンス」と同じ意味である.

メソッドの具体的なコードもクラスに記述されている.このクラスからインスタンスを生成すると,各インスタンスにはこれらのメソッドがそのままコピーされる.(ただし,これは概念上の話で,実際には,1台のコンピュータ内に同じものをたくさんコピーしておくのは無駄が多いので,ポインタなどを用いて効率的に実装されている.)

以上の結果,操作(メソッド)はクラスで共通に使用され,データ(フィールド)はインスタンス毎に異なるものとして使われることになる.

Page 9: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

9

このスライドのプログラムが,これまで設計したロボットをJavaで記述したものである.

Javaで記述するものは,実際にはクラスである.ここではクラス名を「ロボット」としている.

フィールドは,他のプログラミング言語(たとえば,C言語)における変数宣言のような形で宣言される.

メソッドは,(C言語における)関数定義のような形で定義される.

ロボットメソッドのプログラムを書くときのコツは,自分自身がロボットになったつもりで書くことである.

「どこ?」メソッドは,自分が「どこ?」と質問されたつもりで考えて,戻り値として,現在位置の値を返すことにする.

「変速」メソッドは,指定された「新速度」の値に変えよと命令されたつもりになって,自分自身の状態変数である「速度」フィールドに,外部から指定された「新速度」の値を保存する.

Page 10: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

10

「進め」メソッドは,自分が「進め」と命令されたらどうするかを考えて書く.ここでは,現在設定されている速度で1単位時間だけ前進し,燃料が1単位消費される.その結果,「位置」と「燃料」の値が更新される.この場合,正常終了したので,エラーコードとして戻り値0を返すような設計としてある.

燃料が0なら,進むことはできず,エラーコードとして-1を返すことにした.

Page 11: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

11

ロボットのインスタンスが生成されたときに,そのフィールドを初期化する(などの)ために,コンストラクタ(constructor)という特別なメソッドを定義しておく.

Javaでは,コンストラクタの名前はクラス名と同じにするという約束になっている.

したがって,このスライドでは,「ロボット」というコンストラクタを定義している.この定義の内容は,ロボットのインスタンス生成時にプログラマから引数として渡される p, v, f という3つの整数値を,それぞれ,「位置」,「速度」,「燃料」の3つのフィールドの現在値(初期値)として記録(代入)することを表している.(p, v, f は,position, velocity, fuelの頭文字.)

これが典型的なコンストラクタの定義である.すなわち,引数で与えられたデータをフィールド(状態)の初期値として記録するのである.

Page 12: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

12

これはロボットを使う簡単なプログラムである.ロボットを使うプログラムを書くときのコツは,自分がロボットのコントローラを持ったつもりで,いろいろなボタンを押しまくるように書く.ここで比喩的に言っている「ボタンを押す」とは,すでに定義した3つの「メソッドを呼び出す」ことである.

オブジェクト(インスタンス)は,new というキーワードの後に,コンストラクタを呼び出す形式で生成することができる.この例では,new ロボット(...)によって,ロボットのインスタンスを生成し,コンストラクタの記述にしたがって,位置=0,速度=1,燃料=10に設定している.そのロボットを robocop と名付ける.これは robocop という変数を「ロ

ボット型」と宣言し,その変数の初期値として,いま生成されたロボット(への参照(ポインタ))を代入することである.

Javaでは,オブジェクト.メソッド名(引数, ... ,引数)の形の式を書くことによって,オブ

ジェクトのメソッドを引数を指定して呼び出す(実行する)ことができる.このスライドのプログラムでは,Robocop.進め() によって1秒だけロボットを進ませ,つぎに,Robocop.変速(2) によって速度を2に変える.つぎに,while ループの中で,rococo.どこ?( ) によって現在位置を取得し,それが10未満である間,ループの中で進めボタンを連打する.現在位置が10以上になった時点でプログラムは停止する.このようにして,ロボットクラスで

定義された3つのメソッド「進め」,「変速」,「どこ?」を(コントローラーのボタンを押しているかのように)適切に呼び出しながらロボットを動かすプログラムである.

プログラミングの際には,ロボットを作る「メーカー」と,ロボットを買って動かす「ユーザ-」との区別が大事である.メーカーは,ロボットを動かす汎用的なソフトウェア部品をクラスとして制作する.そのプログラムは自分がロボットになったつもりで書く.それに対してユーザーは,自分の応用目的に応じて,やや特別なソフトウェアを(main などとして)制作する.そのプログラムは自分がロボットのコントローラーを持ったつもりで書く.

Page 13: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

13

Page 14: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

14

Page 15: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

15

制作者Aがロボットクラスをプログラムし,制作者Bが船クラスをプログラムしているとし

よう.これらのプログラムは最終的に1つの計算機の同一メモリ内で実行されるので,2人の制作者は連絡を密にして注意深くプログラムを作らないと思わぬミスを生むことがある.たとえば,制作者Bが,船のメソッドの中でロボットのフィールドの値を勝手に変更するプログラムを書くと,ロボットが制作者Aの思わぬ状態変化をして,大変都合が悪い.

そのため,オブジェクト指向の考え方では,基本的に,1つのクラスの中のプログラムが,他のクラスのフィールドの値を直接読み書きすることを禁じている.そのかわり,メソッドの呼び出しを通して,フィールドにアクセスできるようにする.(「読み書き」をまとめて「アクセス」という.)これにより,意図しないプログラムの動作を防止して,バグ(誤り)の生成を抑制し,セキュリティを高めている.

つまり,各クラスはある種の殻によって,内部へのアクセス方法を制限している.あるいは,内部のフィールドを保護している.この機能を「カプセル化」と呼ぶ.このスライドの太いピンク色の部分が殻(あるいはカプセル)のつもりである.カプセルの殻の中にフィールドとメソッドが閉じ込められている.この殻の固さは,ほかのクラスのプログラムが内部のフィールドに直接アクセスすることを防御できる程度に固い.しかし,ほかのクラスのプログラムが自分のメソッドを呼び出すことを許す程度に柔らかい.

カプセルという言葉で,薬が思い浮ぶ.子供などが粉薬(こなぐすり)を飲みやすくするために,オブラートでできているカプセルに入れることがある.カプセルを飲み込むと,カプセルは胃の中で解け,中の薬が胃の中に出てきて吸収される.このカプセルの固さにも注意が必要である.飲むときに破れて口の中に薬がこぼれてしまわない程度に固い必要がある.しかし,胃の中で溶ける程度には柔らかくなければならない.このように,カプセル化は,オブジェクトの内部情報へのアクセスを,適切なレベル(殻の固さ)で保護している.

Page 16: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

16

「オブジェクトのフィールドには,メソッドを通してのみアクセスできる」という概念を図にすると,このスライドのような感じとなる.ちょうど,フィールドがたまごの黄身で,メソッドがその白身であるかのように,フィールドはメソッドによって周囲を囲まれ,守られている.そのため,フィールドの中味にオブジェクトの外部から直接アクセスすることができない.

フィールドにアクセスするには,

オブジェクト.メソッド名(引数, ... ,引数)

の形のプログラムコードを書いて,オブジェクト内のメソッドを起動するしかない.このスライドの図は,このプログラムコードの文法に似せて描いてある.

Page 17: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

17

最も基本的なメソッドは,ゲッター(getter), セッター(setter), コンストラクタ(constructor)である.

ゲッターは,値を読み取る(ゲットする)ためのメソッドである.サッカーなどのスポーツでたくさん点を取る人を point getterというが,それと同じ単語である.ゲッターには,慣習的に,getの後ろにフィールド名を付加した名前を付けることが多い.このスライドのプログラム例の場合,position というフィールドのゲッターは getPosition と命名されている.

セッターは,値を書き込む(セットする,設定する)ためのメソッドである.バレーボールでも,トスを上げてアタックする準備を設定する選手をsetterという.セッターには,慣習的に,setの後ろにフィールド名を付加した名前を付けることが多い.この例の場合,position というフィールドのセッターは setPosition と命名されている.この例では,setPositionの引数で与えられた整数をローカル変数 p で受け取り,セッターのプログラム本体で,それをフィールド変数 position に代入している.ローカル変数 p は,メソッド

の実行が終了したら消えてしまうような,寿命が短い変数である.それに対して,フィールド変数 position は,オブジェクトが存在している限り共に存在するような長い寿命を

持っている.セッターを用いて,暫定的なデータをフィールドに保存することによって,オブジェクトの状態が変更されることになる.

コンストラクタは,すでに見たように,インスタンス生成時の初期設定を定義している.コンストラクタの名前はクラス名と同じでなければならない.オブジェクトを新規に生成して,コンストラクタを呼び出してフィールドを初期化するには, new コンストラクタ(引数, … ,引数) という形式を用いる.

Page 18: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

18

この例では,Position の値を10にセットするために,setPositionというセッターを使用している.

Page 19: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

19

いつもクラスを定義したJavaのソースコードを見るのは大変なので,設計内容の概略を

このようなクラス図として図式化することが多い.長方形を3段に分け,最上段にはクラス名を,中段にはフィールド名(とそのデータ型名)を,最下段にはメソッド名(と引数及び戻り値のデータ型名)を記入する.

このような情報は,クラスを利用して応用プログラムを作成しようとするプログラマーに対して,利用法の「インタフェース」(ソフトウェア部品の使い方)を提供しているので,API (Application Programmer's Interface)と呼ばれることがある.

Page 20: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

20

これはこれまで作ってきた「ロボット」クラスのクラス図である.

「どこ?」メソッドはゲッター,「変速」メソッドは セッターになっている.

「進め」メソッドはゲッター, セッターのような基本的なメソッドではなく,このロボット特有の応用的なメソッドである.

Page 21: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

21

Page 22: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

22

オブジェクト指向の特徴は,すでに作ったコードを再利用して機能拡張をしていく仕組みが整っていることである.それが継承(インヘリタンス)という機能である.

この例では,これまで作成したロボット(使い捨て)を拡張して,再利用可能ロボットを作ろうとしている.つまり,燃料が切れたら,燃料補給できる機能を追加する.

新しいフィールドとして燃料タンクの「容量」を追加する.

新しいフィールドとして「補給」メソッドを追加する.

このようなクラス間の関係を,図のように,親/子と呼んだり,スーパークラス/サブクラ

スと呼んだりする.「ものつくり」の観点から言えば,スーパークラス(親クラス)は部品であり,ソフトウェア部品メーカーが供給することもある.それを目的に合わせてユーザ側のプログラマーがカスタマイズしたり機能拡張するなど,有効に再利用して作るものがサブクラス(子クラス)である.

Page 23: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

23

Javaではこの図のように

extends 親クラス名

と書くことによって,親クラスを再利用して子クラスを定義できる.この例では,すでに作成した「ロボット」クラスを親クラスとし,その子クラスとして「再利用可能ロボット」クラスを定義している.

子クラスには,新しく追加するフィールドやメソッドだけを記述する.すでに親クラスで定義してあるフィールドやメソッドは,書かなくても,あたかも書いてあるかのように,子クラスに引き継がれる.

また,この子クラスをコンパイルする際には,スーパークラスを書き直したり,再コンパイルする必要はない.さらに重要なのは,スーパークラスのソースコード(ソースファイル)は無くてよく,そのコンパイルされたコード(クラスファイル)さえあればOKである.これは特にスーパークラスが商用で,価格のある商品の場合に重要である.スーパークラスを作成したソフトウェアメーカーは,それを販売するにあたって,ふつうはソースコードを公開したくない.公開すれば他社等に設計情報を知られるからである.Javaの場合,スーパークラスを購入したユーザーは,APIだけを知っていれば,ソースファイルがなくても,

クラスファイルさえあれば,購入したスーパークラスを部品として利用し,子クラスを自分で定義できるのである.

Page 24: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

24

クラス図を書くときには,子クラスから親クラスに矢印を付けておく.

Page 25: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

25

再利用可能ロボットを使ってみよう.この例では,再利用可能ロボット robo2を生成し,forループを使って「進め」ボタンを100回連打する.ただし,途中で燃料切れになったと

きは,「進め」メソッドは負のエラーコードを返すので,燃料を補給して,ボタンを1回押し直している.

Page 26: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

26

Page 27: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

27

このスライドに図示してある3つのクラス(ロボット,船,先生)に,いずれも「進め」メソッドがあるとしよう.3つのメソッドの内容(プログラムコード)は全く異なるものである.ロボットが「進む」とは,足を動かすプログラムコードを実行することだし,船が「進む」とは,スクリューを回転させるコードを実行することである.先生が「進む」とは,脱線していた授業を進ませることである.したがって,本来なら異なるメソッド名を付けるのがスジである.

しかし,人間にとって「進め」という言葉が適切なら,その言葉を共通に使えるようにした方が便利である.言葉の数の節約になり,人間にとって心理的に覚えやすい.

プログラミングの世界でその考え方を採り入れたのが「ポリモーフィズム」という機能である.(このスライドでは「多相性」という和訳を採用したが,「多態性」,「多様性」などと呼ばれるときもある.カタカナ書きでも,「ポリモフィズム」,「ポリモルフィズム」などの表記が使われることがある.)

オブジェクト指向の言葉で述べると,「同じメッセージ(メソッド)でも,受け手のオブジェクトのクラスによって,処理(意味)が異なる」という機能である.ここで「同じメソッド」と言っているのは,一般には,メソッドの名前が同じであることに加えて,そのインタフェース(すなわち,引数の数と型,およびメソッドからの戻り値の型)までも同じであることを指している.

Page 28: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

28

「ロボット」クラスのオブジェクトは「ロボット」型というデータ型である.同様に,「船」クラスは「船」型,「先生」クラスは「先生」型である.これらのクラス(型)は,共通に「進め」という同一の名前をもつメソッドをもっている.

そのような共通性を認識して,これらの複数のデータ型をまとめて,1つのデータ型として総称的に定義することができる.このスライドの例ではそれを「進めるもの」という名前のデータ型としている.

Page 29: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

29

Javaでこの考え方を実現する方法の1つが,「インタフェース」という機能である.その書き方はクラスの書き方と似ているが,class のかわりに interface というキーワードを用いる点が異なる.最も重要な違いは,メソッドの本体({ と }で囲まれて記述されるプログラムコード)を書くことが許されない点である.そのようなメソッドを抽象メソッドという.

このJavaコードの例は,「進めるもの」は,

int 進め();

というメソッドを共通に持っていることを宣言している.

Javaで良く使われるインタフェースとして,Runnable インタフェースというのがある.これは,runメソッドを持つクラスを総称するためのもので,実際にはつぎのように定義されている.

interface Runnable {

void run( );

}

Page 30: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

30

ロボット型が,進めるもの型に含まれることを,implements というキーワードを用いてこのスライドのように記述する.

このような場合,「ロボット」クラスは「進めるもの」インタフェースを「実装している」という.Javaの約束によって,インタフェースを実装するクラスの中では,そのインタフェース

内で記述された抽象メソッドの本体を具体的に記述する必要がある.この例の場合,「進めるもの」インタフェースには,int 進め( ) という抽象メソッドが記述されていたことを

思い出そう.したがって,「進めるもの」インタフェースを実装する「ロボット」クラスの中で,int 進め( ) メソッドの本体が具体的に記述されている.

Page 31: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

31

ロボットが「進めるもの」を実装していることを,破線の矢印を使って,このようなクラス図で表す.

Page 32: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

32

ポリモーフィズムが便利と感じるようなプログラムの例を紹介しておこう.それは同じインタフェースが実装されたいろいろなクラスのオブジェクトを一次元配列に記憶しておき,それらに対して同じメッセージを送る(同じ名前のメソッドを実行する)ことにより,各オブジェクト特有の適切な動作を統一的なコーディングによって起こさせる例である.

Java で整数(int)の一次元配列を作るには,つぎの例のようにする.

int [ ] a = new int[n]; または int a[ ] = new int[n];

これは,n個の要素 a[0],a[1],...,a[n-1] をもつint型の配列を生成し,a という名前の変数に代入している例である.int 型以外の配列を作るときには,型の名前を int でない適切なものに変えればよい.

このスライドのプログラムでは,「進めるもの」型のオブジェクトを3つまで記憶できる配列を用意して,Aという名前の変数に代入している.プログラムの第2~4行目では,Aの3つの要素に,それぞれ,ロボット,船,先生のインスタンスを記憶させている.

そもそもポリモーフィズムがなければ,こういうことはできない.なぜなら,配列というものは,同じデータ型のデータを複数個記憶するためのデータ構造だからである.たとえば,整数型や実数型や文字列型のデータを混在させて記憶することはできないのである.

しかし,ポリモーフィズムがあればそれができる.この例では,ロボット,船,先生は異なるデータ型で,ふつうは同じ配列に混在させることはできないのだが,それらの3つのデータ型を包含する「進めるもの」型の配列を導入することにより,すべてを記憶できるようになる.なぜなら,ロボットも船も先生も「進めるもの」型という同じデータ型に属するからである.

Page 33: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

33

さて,つぎに進もう.ここが山場である.前のコードに続いて,このスライドでは for 文が書かれている.for ループの中で,i を0,1,2というふうに変化させながら,ループの本体 A[i].進め( ); が3回実行されるのだが,そこでは「進め」という同じ名前のメソッドによって,異なる意味と異なるメカニズムのもとで A[ i ] に記憶されているオブジェクトが「進む」こととなる.すなわち,ループの第1周目(i=0)ではロボットが足を動かしながら進み,第2周目では船がスクリューを回転させて進み,第3周目では先生が授業を進めるというバラエティに富んだ処理が行われる.要するに,全員に一斉に「進め」という指示を与えたときに,各自がそれぞれの「進め」の意味に従って行動する様子が表現できているのである.

このような処理を簡潔な1行で記述できるのは大変便利である.ポリモーフィズムがなければ,オブジェクトがどのクラスのインスタンスなのかの判別に instanceof というJavaの機能を用いて,if 文を使って,

for( i=0; i<3; i++)

if (A[i] instanceof ロボット) A[i].ロボット進め( )

else if (A[i] instanceof 船) A[i].船進め( )

else if (A[i] instanceof 先生) A[i].先生進め( );

のように,インスタンスのクラスが何であるかに応じて,「ロボット進め」,「船進め」,「先生進め」という,名前の異なるメソッドを実行させることになる.その程度のことはたいした面倒なことではないのだが,プログラムのバージョンアップのときに「進め」を実行できる第4,第5のクラスが導入されてくるとその違いが明確になる.ポリモーフィズムがあるときは,第4,第5のクラスをこれまでのように定義すればそれで済み,このスライドの forループのプログラムを書き直す必要はない.しかし,if 文を使うプログラムでは,if 文の条件判定を新クラス向けに追加しなければならないので,このfor文を書き直し,再コンパイルする必要が生ずるのである.

このようなプログラミングテクニックは Java のプログラムコードでは実にたくさん見かけるものなので,ぜひ覚えておこう.良く知られているものは,Runnableインタフェースである.Javaでは,「アニメーションを動かすと同時に,GUIのクリックも受け付ける」などのように,複数のプログラムを同時に動かすときには,スレッド(Thread)と呼ばれるオブジェクトを使う.その場合,プログラマは,応用に合わせて,オブジェクトに,void run( ) というメソッドを記述しておく必要がある.実はこの run ( )メソッドはJavaが事前に用意しているRunnableというインタフェースの中で記述された抽象メソッドである.プログラマは,Runnableインタフェースを実装するようなクラスを自分で定義し,その中でrun ( )メソッドの中身を具体的に記述するのである.つまり,先ほどの説明と比較すると,「進め」メソッドが run ,「進めるもの」インタフェースが Runnable に対応している.

Page 34: Java - 北海道大学lis2.huie.hokudai.ac.jp/~kurihara/classes/AI/object.pdfの引数として関数を渡したり,関数のコードの中で関数を動的に生成して,その関数を

34