TSSI 基盤技術研修コース— プログラミング言語 — ＃ 2
久野靖∗

2011.5.27

はじめに1□ じゃあ、Java がいいの?

さらに検討してみましょう…

□ 皆様のアンケートを拝見しました。

2.1

値とオブジェクト

• 普段プログラムを書かない方は苦戦しているようです → 「プログラミング言語」講座なのでご理解を。 □ C++も Java も「値」と「オブジェクト」を区別している。
• ソフトウェア開発をやられている方はだいたいご存• 値〜数値、文字、論理値。

じですが→ そのような方は「概論」的な話に興味が• オブジェクト→ C++でも Java でもクラスにより定義。

あるので、概論もできるだけやって行きます。

• ポインタ型/参照型→ C++にのみ存在。Java ではオ• 「演習問題をやるのはよかった」ですが、これから
はコードが長くなるので、演習は難しいかも。できブジェクト値は全部ポインタのようなもの。
□ C++と Java で一番違うところは:るだけ考えます。

1.1• C++ではオブジェクトは「配列や実行スタック上に直接その記憶域を配置できる。外に配置するときはポ

第 2 回の構成

インタを使う」□ C++と Java の基本部分 (構造、例外処理など)。両者の• Java ではオブジェクトは「すべてヒープ上にあり、違うところから、プログラミング言語の設計思想によっ

ポインタで指す」(Java の用語では「参照」) →最もてどのような違いが現れて来るかを具体的に見る

重要な単純化。動的データ構造や多態のためにはど□ オブジェクト指向言語の基本概念、さまざまな言語とそのみち参照が必要。そのため、すべてポインタに統れらに見られる概念一している。そのために遅い面も。

• グラフィクス/GUI を用いた例題→分かりやすい (見て分かる)、ただし例題は Java のみ

class point {double x, y;... }p□ オブジェクト指向言語の実装技術→必要な箇所にそのつC++

ど挿入 (諸概念が出て来たところで説明した方が納得し

y

やすい)

2

p

x

Java

r
C++

□ 前回やったこと…C++と Java で簡単な例題プログラムxyxを読んだり書いてみたりした→分かった違いは?

yC++class Rect {Point p1, p2;... }オブジェクト指向言語入門 (つづき)

x

r

x
Java

y

C++

y

• C++はヘッダと本体と分けて書く必要 (← C の嫌なところがまだある)xy• そのため、Java ではごみ集め (GC、後述) が必須。

• C++は演算子定義が可能だったり const を明記でき

C++ではごみ集めはオーバヘッドがあるので言語本体としては提供しない→手動メモリ管理が前提 (うまたり Java にない機能が色々ある• Java は書き方がきれいでシンプル←割り切って整理されている

くやれば効率はよいが面倒、間違えるとメモリリー
クしたり落ちたりする)。

∗ 筑波大学大学院経営システム科学専攻1□ C++はオブジェクトも書き方を値に近くできる。Java は2.2

それはしない。

Java とごみ集め (GC)□ Java のコード→どんどん必要なオブジェクトを生成し、• C++では「==」などの演算子を定義できる→オブジェ
クトでも演算子を使って書く。

使わないものは GC で回収、というスタイル。

• GC のオーバヘッドは織り込み済みという割り切りが必要。

• Java では「値→演算子」「オブジェクト→メソッ
ド」ときっぱり区分されている。
「v1 == v2」(値)、• 自分で領域開放するよりずっと楽。自前でやるとコー「o1.equals(o2)」。もしオブジェクトに「o1 == o1」

ドが複雑化し、また重大な誤りの原因になりやすい。

を使うと「ポインタ比較 (同一のオブジェクトかど• GC は Lisp あたりでは古くから使われているが、普うかを調べる)」になる。

通の手続き型言語で標準としたのは Java がはじめて□ C++でのオブジェクトの初期設定と後始末は…

□ GC の原理:• コンストラクタとデストラクタ (名前が「〜クラス名」) による

変数から参照できるオブジェクト群を順次たどって行き、たどったという印をつける。印がつかな
かった領域はごみとして回収• ローカル変数のオブジェクトはスコープに入った時にコンストラクタが呼ばれ、出るときにデストラク• その他、使っているオブジェクトだけコピーする方
式、オブジェクトがそれぞれ何個所から指されていタが呼ばれる。

るか常に数えておく方式などもある。

• ヒープ上のものは「new クラス名 (…)」でメモリ割• GC で自動的に回収するためには「不要なデータ構造
は指さないようにする」(指している所に null を入り当て+コンストラクタ、「delete オブジェクト」でデストラクタ+メモリ開放れるなどしてつながりを切る) ことが必要• 配列の場合「new クラス名 [大きさ]」で割り当て+
デフォルトコンストラクタ (引数なし)、「delete[]• 「もう要らないからこれ回収して」とは言えない (なんで?)ポインタ」でデストラクタ+メモリ開放。(ポインタが単一要素か配列参照か分からないという弱点は C
言語から引き継いでいる。)

2.3

整数の集合: C++版

□ Java の場合は「new クラス名 (…)」でコンストラクタ □ より複雑なものの例として「整数の集合」型を作ってみる。
というのは C++と同じ。配列はポインタ配列なので初期値 null。領域回収は GC なのでずっと簡単。後始末用

static const int maxsize = 100;にはメソッド finalize()、ただし GC が領域を回収すclass Intset { // set of ints
int count;
int arr[maxsize];
void add1(int x); // internal use only
public:Intset();int size() const;bool is_in(const int i) const;
Intset operator+(const Intset &s) const;
Intset operator-(const Intset &s) const;
Intset operator*(const Intset &s) const;class Overflow { };friend ostream& operator<<(ostream& o, Intset& s);friend istream& operator>>(istream& i, Intset& s);};

るときに呼ばれるので、呼ばれない場合も。
scope
entry

Ｃ＋＋
（直接）

Ｃ＋＋
（ヒープ）

Ｊａｖａ
常にヒープ

{ Obj x;{ Obj *x =new Obj();{ Obj x =new Obj();

…

…

…

…

…

…

}

delete *x;

}

}
scope
exit
scope
entry

scope
exit

自動的に
コンスト
ラクタ
呼出

明示的に
コンスト
ラクタ
呼出

自動的に
デスト
ラクタ
呼出

明示的に
デスト
ラクタ
呼出（必須）

Ｃ＋＋
Obj::Obj() {...
}Obj::~Obj() {
...
}

明示的に
コンスト
ラクタ
呼出
何もしない
（ＧＣ時にあれば後始末を呼出）• 実装の方針→配列を用いて整数の列を保持。とりあ
えず最大固定。

• インタフェースはとりあえず最低限:

Ｊａｖａ
class Obj {public Obj() {
...
}
...

□ 実装:
Intset::Intset() { count = 0; }void Intset::add1(int x) {if(count+1 >= maxsize) throw Overflow();arr[count++] = x;public void finalize() {
...
}
}

2

case ’*’: cin>>x; r = r * x; break;}default: continue;
int Intset::size() const { return count; }}bool Intset::is_in(const int i) const {cout << r << ’\n’;
for(int k = 0; k < count; ++k)}if(arr[k] == i) return true;}
return false;}
Intset Intset::operator+(const Intset &s) const {Intset r = s;
2.4 整数の集合: Java 版
for(int k = 0; k < count; ++k)if(!s.is_in(arr[k])) r.add1(arr[k]);□ main 側。これもこれまでと同様。
return r;}import java.util.*;
Intset Intset::operator-(const Intset &s) const {Intset r;
public class Sample14 {for(int k = 0; k < count; ++k)
public static void main(String[] args)
if(!s.is_in(arr[k])) r.add1(arr[k]);throws Exception {return r;
Scanner sc = new Scanner(System.in);}
IntSet r = new IntSet();Intset Intset::operator*(const Intset &s) const {while(true) {Intset r;
System.out.print("? ");for(int k = 0; k < count; ++k)
String s = sc.nextLine();if(s.is_in(arr[k])) r.add1(arr[k]);if(s.charAt(0) == ’q’) return;
return r;
IntSet x = new IntSet(s.substring(1));}
switch(s.charAt(0)) {ostream& operator<<(ostream& o, Intset& r) {case ’=’: r = x; break;o << "{ ";case ’+’: r = r.add(x); break;for(int k = 0; k < r.count; ++k) o << r.arr[k] << ’ ’;case ’-’: r = r.sub(x); break;o << ’}’; return o;case ’*’: r = r.mul(x); break;}default: continue;
istream& operator>>(istream& i, Intset& r) {}r.count = 0;
System.out.println(": " + r);
while(i.peek() != ’\n’) {}int x; i >> x; r.add1(x);}}}i.get(); return i;}□ クラス側• 下請けメソッド add1() はあふれチェックして要素をclass IntSet {int[] arr;
int count = 0;
public IntSet(String s) {try {StringTokenizer tok = new StringTokenizer(s);arr = new int[tok.countTokens()];for(int i = 0; i < arr.length; ++i) {int x = Integer.parseInt(tok.nextToken());
if(!is_in(x)) add1(x);}} catch(Exception ex) { }}public IntSet() { arr = new int[0]; }private IntSet(int c) { arr = new int[c]; }private void add1(int x) {if(count+1 >= arr.length) {int[] a = new int[arr.length*2 + 1];
for(int i = 0; i < count; ++i)a[i] = arr[i];arr = a;}arr[count++] = x;}public int size() { return count; }public boolean is_in(int x) {for(int i = 0; i < count; ++i)if(arr[i] == x) return true;
return false;

追加する。

• 演算のメソッドは 2 つの集合に含まれているかどうかをそれぞれチェックしながら動作。
• 出力は書くだけ。入力は読み込みながら add1() を呼ぶ。
□ メイン部分は先の例とほとんど代わらない#include <iostream>#include <string>#include <cstdlib>
using namespace std;// ここにヘッダ部分// ここに実装部分
int main(void) {Intset r, x;
while(true) {char c; cout << "? "; cin >> c;
if(c == ’q’) return 0;
switch(c) {case ’q’: return 0;case ’=’: cin>>x; r = x; break;case ’+’: cin>>x; r = r + x; break;case ’-’: cin>>x; r = r - x; break;3

• add1() で配列が満杯になったら増やす。
}
public IntSet add(IntSet r) {□ クラス定義部分のコードを示す:
IntSet s = new IntSet(size() + r.size());
for(int i = 0; i < count; ++i) s.add1(arr[i]);for(int i = 0; i < r.count; ++i)class Intset { // set of ints
if(!is_in(r.arr[i])) s.add1(r.arr[i]);int count, limit;
return s;
int *arr;}
Intset(int c);public IntSet sub(IntSet r) {void add1(int x);
IntSet s = new IntSet(Math.max(size(), r.size()));
static int min(int x, int y) { return (x<y)?x:y; }for(int i = 0; i < count; ++i)
public:if(!r.is_in(arr[i])) s.add1(arr[i]);Intset();return s;
Intset(const Intset &s);}
~Intset();public IntSet mul(IntSet r) {Intset& operator=(const Intset& s);
IntSet s = new IntSet(Math.max(size(), r.size()));
int size() const;
for(int i = 0; i < count; ++i)
bool is_in(const int i) const;
if(r.is_in(arr[i])) s.add1(arr[i]);Intset operator+(const Intset &s) const;
return s;
Intset operator-(const Intset &s) const;}
Intset operator*(const Intset &s) const;
public String toString() {friend ostream& operator<<(ostream& o, Intset& s);if(count == 0) return "{ }";friend istream& operator>>(istream& i, Intset& s);String s = "";};for(int i = 0; i < count; ++i)s += " " + arr[i];• ヘンなコンストラクタと演算子が増えている?
return "{" + s.substring(1) + "}";}□ QUIZ: あるクラス Someclass の変数 x、y があるとする。

}□ 次の 2 つは同等だと思う?

• Java では配列は常に「別オブジェクトへの参照」になる(A) Someclass x = y;

• 必要な容量を見積もってその大きさで用意し、容量が足りなくなったらより大きいものを用意して差し □ 前問の答え:替え。

YES/NO(B) Somclass x;
x = y;「初期化」と「代入」は別物• 初期化は「ゼロから内部データ構造を作る」• 使わなくなった古い配列はごみ集めにより回収• 代入は「できているデータ構造に格納する」□ 次の場合は初期化か代入か?

2.5

C++のコピーコンストラクタと代入演算子(A) int f(Someclass x) (B) Someclass f() {...
...
f(y) ←呼び出し
return y; ←返値}□ 整数の集合:C++版でも配列が足りなくなった場合に対処するには?
Intset::Intset(int c) {count = 0; limit = c; arr = new int[c];}Intset::Intset() {count = limit = 0; arr = new int[0];}Intset::~Intset() { delete[] arr; }void Intset::add1(int x) {if(count+1 >= limit) {int *a = new int[limit*2+1];
for(int i = 0; i < count; ++i)a[i] = arr[i];delete[] arr; arr = a;limit = limit*2+1;}
arr[count++] = x;}

□ 前問の答え:

いずれも「初期化」 。引数は実引数のコピーで初期化される。返値は返値用のテンポラリがreturn の式で初期化される。
□ 「初期化」も「代入」も内部データ構造に応じたものを用意しないとまずい*場合がある*→「コピーコンストラ
クタ」と「代入演算子」
Intset(const Intset &s);
Intset& operator=(const Intset& s);• 指定しない場合は「各メンバごとのコピー」が用意される。それだとどういう場合にまずいか分かりますか?
□ 通常のコピー (引数渡し等)、代入動作→オブジェクト• 初期化時に配列サイズを決めて割り当てる。

のそれぞれのインスタンス変数をコピー。先のバージョ• デストラクタが必要に (このオブジェクトが不要に

ンではこれでよかったが…

なったら指している配列も不要になるから。

4• 変数 arr をコピーする→ポインタのコピーであって配列はコピーされない (共有された状態になる) →そ• 例えば先の整数集合でも「add1(x) で直接追加」を公開にすると書き換え可能になるれでは困る (副作用とか)。

• 配列オブジェクトなどが書き換え可能なものの典型例• このため、「コピーコンストラクタ」と「代入演算子」
を作成して、その中で arr が指す配列をコピーする。

• CLU など「書き換えられない配列」を持つ言語もあった• 書き換え可能だと変更する時の効率はよい

Intset y = x;
何もしない
場合
起こって
欲しいこと

y

x
3

5

3

3

3

• その代わり思わぬ副作用が起きないか注意する必要
がある

5

y
1 3 7

□ 自分がクラスを作る時はどちらにするか十分検討して考

初期化

える

1 3 7

y = z;
何もしない
場合

y
5

？
起こって
欲しいこと

□ 「論理的には不変」(外からオブジェクトを使っている

z
2

2

5

1 3 7

限りは副作用がない) でも、「物理的には可変」ということがある。

8 9

y
2

• 例:

3
代入

文字列表現を常に作るのでは無駄だから最初に必要とされた時に作り、一度作ったら覚えておいて

8 9

再利用。
□ C++では const を指定することで「不変」を明示できる
が指定は厄介□ 具体的なコード (この種のものとしては典型的)
Intset::Intset(const Intset &s) {arr = new int[s.count];
limit = count = s.count;for(int i = 0; i < count; ++i)
arr[i] = s.arr[i];}Intset& Intset::operator=(const Intset& s) {if(this != &s) {count = 0;
for(int i = 0; i < s.count; ++i)
add1(s.arr[i]);}return *this;}const int i = 100; // i は不変
const int *p = &i; // p は不変だが*p は書ける
int *const p = &i; // p は書けるが*p は不変
const int *const p = ... // 両方不変
int size() const; // メソッドは副作用を持たない□ 「意味的には不変だが内部的には可変」なことも…→何らかの工夫が必要• 例: 書き換え対象となる一部の変数に「mutable」と指定。
• 例: 書き換え対象となる部分は別オブジェクトにする。

• 代入の場合は「自分への代入は何もしない」を入れておくのが通例。

2.7
2.6

書き換え可能/書き換え不能

例外処理□ 最近の言語で加わった新しい制御構造 (Lisp などでは
古くからある)□ 書き換え不能 (immutable) →オブジェクトの状態が変□ もともとの問題:

化しないことエラー検出はどうやるのがいいのか?

status = some_func(...);
if(status != OK) {エラー処理…
}• Rational オブジェクトも Intset オブジェクトも内部的には変数を持っていて値が変化することは可能だが、外からメソッド経由で使っているぶんには値を変化させることはできない (論理的な不変性)• どういう問題があるか?

• 書き換え不能だと副作用の心配がない□ 返り値が使えなくなってしまう• その代わりちょっとでも変更したいときは新しいオブジェクトを作ることになる□ 広域変数にするとマルチスレッドとか問題□ 書き換え可能 (mutable) →オブジェクトの状態が変化 □ エラー処理が各所に挿入されると処理の流れが見づらいすること• どこか 1 箇所で処理したいが goto はあまり使いたくない5• エラーに飛び出したために後始末が飛ばされたりす
ると困る

Throwable ←例外すべて
Error ←システム上の問題
OutofMemoryError...
Exception ←普通の例外
IOException
InterruptedException
RuntimeException ←どこでも起きるような例外
NumberFormatException
NullPointerException...

□ エラー処理のための制御構造を導入→「例外」処理
try {... 何かあるかも知れない... 処理をいくらでも書く} catch(Exception ex) { ←エラー情報受け取り
ここでまとめて処理}• C++では言語上は例外オブジェクトは何でもよい。す□ 例外を発生させる側は…

べてを受け止めたい場合は「catch(...)」というヘ

throw new 例外クラス名 (...); //Java
throw 例外クラス名 (); //C++

ンなものを使う。

• ここまでの書き方は Java と C++でほとんど同じ。
Java ではオブジェクトは全部ポインタだからこのままでいいが、C++では受け取るところをコピーを避ける□ メソッドがどのような例外を返すかはコンパイラがチェック可能。

ため参照で受け取るのが普通。「catch(Exception&• いずれもメソッド引数の後に、C++は「throw (名前,…)」Java では「throws 種別, ...」という形。

ex)」• C++では「書かないと何でもよい」「受け止めなくてもよい」□ 「必ずやりたい後始末」(例:ファイルの close など) はどうするか?

• Java では「原則として自分が発生させる例外は明示」←途中から飛び出されるのでメソッド末「自分が呼ぶメソッドで発生する例外は原則として受尾を通過しないかも

け止めて処理」• Java では finally ブロックにそのような処理を指定• Java で受け止めないでもいいのは自分も同じ例外を

try {
...
try {
... ←この中で起きたエラーで} catch(NumberFormatException e1) {... ←数値書式エラーはここへ来る} finally {必ずやる後始末}...
} catch(Exception ex) {... ←他のエラーは直接ここへ来る}

throws で明示している場合のみ。ただし、すべてのメ
ソッドは「throws Error, RuntimeException」ははじめから指定されているものと見なされる

2.8

この節のまとめ□ C++は C を土台にオブジェクト指向機能を追加して作った言語• ポリシーとして「C と同程度の実行効率」
「多様な選
択肢」

• C++は finally がない→ローカル変数のデストラク
タ等を活用• このため「プログラマが責任を持つ」ことは多め。
複雑。

{ Someclass x;... ←途中からどうやって飛び出したとしても...
} ← Someclass::~Someclass() が必ず呼ばれる□ Java は C++のアンチテーゼとして「安全優先」で作られた言語□ 受け止めなかった例外は?

• このため「最初から危ないことはできない」言語設計。効率は犠牲に。

• メソッド呼び出し側に返される (そこに try-catchがあればそこで受け止められる)□ いずれも新しい制御構造として「例外」機構を導入して• 最後まで受け止められなければプログラム実行を中止いる。

□ 例外種別の分類→階層構造□ ここまででは抽象データ型 (ADT) としてのクラスの利用に限定して説明した (これだけでも十分重要だし複雑)。

• Java で は す べ て の 例 外 が 階 層 構 造 に 統 一 →Throwable を受け止めれば必ずすべてが受け止められる6オブジェクト指向言語とその諸概念

3

暑いよ！
window□ オブジェクト指向の基本的なアイデアは簡単□ 実際に言語として設計すると多くの考慮点

閉めて
いい？

□ 歴史的な推移に従って見ていくと分かりやすい

止めて
いい？

3.1

sensor

オブジェクト指向の定義□ オブジェクト指向とは、プログラムが扱う対象のそれぞ

寒いよ！

れを自立した「もの」として扱う「考え方」□ オブジェクト指向が取り入れられている分野はいろいろ• オブジェクト指向→「気温センサ」「ヒーター」「窓開閉装置」などの「もの」を考える→「気温センサ」

ある• オブジェクト指向プログラミング

は温度が低いと「ヒーター」、高いと「窓」に注意を• オブジェクト指向ソフトウェア工学 (分析、設計、開発、UML…)

喚起→「ヒーター」は注意を喚起されると、定期的に「センサ」に温度を尋ね、一定以下だと通電、十分暖
かいならヒーターを止めて仕事を終る→人間にとっ• オブジェクト指向ソフトウェア技術 (コンポーネン
ト、分散オブジェクト、…)て考えやすく、適度な大きさに分けて考えられる。

• オブジェクト指向データベース□ …で。

• ここでは「言語」を中心に扱う

3.2

heater• 「結局、従来と同じことをやってる」と思いますか?
• 「それは大変よさそうだ」と思いますか?

オブジェクト指向的な考え方□ 「オブジェクト指向が」よいかどうか…

□ たとえば、「温室の温度調節システム」を考える。

• プログラムの動作そのものは同じことをさまざまに書ける。当然。

window• 人間にとって考えやすくさせてくれるならそれは「よいこと」。

• 問題は「その分だけ余計な概念が増えて負担になる」
ことを差し引いてトータルで儲かっているかどうか。

Ｃ□ 現在大量に書かれている C++や Java のコード→ある意

sensor

味では「儲かっている」ことの傍証かも (盲目的にそう信じることはできないが)。

heater□ 旧来の考え方→手続き+受動的なデータ

3.3

本節のまとめ• 「センサーを見て、気温が下がってきたらヒーター □ 「オブジェクト指向」とは「考え方」を通電するが、温度が上がりすぎたらヒーターを切□ 「もの」と「動作」→我々の日常に近い→考えやすい→る」「気温が上がってきたら窓を開くが、下がってき同じ労力でより複雑なものまで考えられる
たら閉める」など機能中心に考える□ オブジェクト指向言語→オブジェクト指向のための機能• 制御する要素や条件が複雑になるとごちゃごちゃにをさまざまに追加 (次節で見て行く)なりやすい。
• 色々追加したことによる複雑さと見合うかどうかが問題□ オブジェクト指向だと…

7

オブジェクト指向言語の基本部分

4}□ 「最初のオブジェクト指向言語」って知っていますか?
□ Simula →最初のオブジェクト指向言語• 開発されたのは 1960 年代半ば。最終版は Simula67• もともと「シミュレーションのための言語」→「もの」を「まねする」しくみとしてオブジェクト指向を導入• しかし現在のオブジェクト指向言語に見られる基本的な仕組みはすべて持っている→極めて先進的• Simula が持っていた機能 → クラス、インスタンス、

class Car {String name;double speed;
public Car(String n, double s) {name = n; speed = s;}public void changeSpeed(double d) {speed += d;}public void showInfo() {System.out.println("Car: " + name +" speed: " + speed);}}

カプセル化、メソッド、メッセージ送信記法、継承、% javac Sample21.java% java Sample21
Car: My Car speed: 50.0
Car: Father’s Car speed: 80.0
Car: My Car speed: 30.0%

包含型、動的分配• その後追加された基本概念→ インタフェース、抽象クラス、入れ子クラス、メタクラス、自己反映機能、…

• この節では「基本部分」として、Simula が持っていた機能プラスインタフェースの範囲を取り上げる。例// C++#include <iostream>#include <string>#include <cstdlib>
using namespace std;

題は Java 言語による。

4.1

クラスによるオブジェクト定義□ オブジェクト:さまざまな「種類」がある (はず)。例:

class Car {const string name;double speed;
public:Car(string n, double s);
void change_speed(double d);void show_info();};「乗用車」「トラック」「バス」□ 1 つの種類のオブジェクト:

複数ある (はず)。例:「私の車」「実家の車」□ 「種類」を「クラス」(と呼ばれる単位) で記述し、「クラス」を雛型にインスタンス (個々のオブジェクト) を

1 つ以上生成

Car::Car(string n, double s) :name(n), speed(s) {}void Car::change_speed(double d) {speed += d;}void Car::show_info() const {cout << "Car: " << name << " speed: "<< speed << "\n";}□ クラスに規定されるもの• インスタンス変数 (状態変数とも呼ぶ、C++ではメン
バ変数) →各インスタンスの「状態」ないし「固有の値」を保持• メソッド (C++ではメンバ関数) →各インスタンスに付随する手続き。この手続きの中では、インスタン
ス変数の読み書きが可能。
□ Simula の用語ではインスタンスは「永続的なブロック」、
インスタンス変数は「ブロックの実行が終わっても値が消滅しないようなブロックローカル変数」と位置付けていた。しかし意味的には上記のように、現在のオブジェ
クト指向言語と同じ。

int main() {Car c1 = Car("My Car", 50);
Car c2 = Car("Father’s Car", 80);
c1.show_info(); c2.show_info();c1.change_speed(-20); c1.show_info();}□ 今回は string クラスを利用。文字列定数 (char*) から// Javapublic class Sample21 {public static void main(String[] args) {Car c1 = new Car("My Car", 50);
Car c2 = new Car("Father’s Car", 80);
c1.showInfo(); c2.showInfo();c1.changeSpeed(-20);
c1.showInfo();}string への自動的な初期設定 (=変換) が使われている

4.2

クラスの実現方法□ クラスの実現はごく簡単→レコード型だと思って変数の割り当て等を計算すればよい。

8• C や C++のように「別の型だと強制的に見なすキャス
ト (reinterpret cast)」があると問題• オブジェクト生成時にその大きさの領域を用意し、初期設定する (コンストラクタを書かない場合は標準の初期設定)• 動的な (弱い型の) 言語では実行時にクラス情報を参照して検査□ 変数の読み書きは領域先頭からの固定オフセットに対し□ インスタンスを「その場に」取るような言語ではカプセ
ル化はしてもコンパイラには利用するクラスの中が見えないとまずい。例:C++ (他にも Ada や Eiffel など)て行えばよい□ 動的な言語では変数の位置も探索する場合がある• 多くの変数があり、一部にしか値を設定しない場合には有利かもclass Human { ↓以下は見ちゃだめ
char *name; float height, weight;
public:↓以下は見ていいchar *getName();float getWeight();...
};• 多重継承の場合にも有利 (後述)
□ 通常、動的分配のための手当てが必要 (後述)

• 動的分配を使わないならデータ領域だけでよい (C++など)

□ こういう情報がヘッダファイルに入っている。

• つまり「見ていい」「見ちゃだめ」を区別…とはいっ

4.3

カプセル化

てもそこに書いてあるという嫌らしさ• さらに、後で変数を増やすとコンパイルし直しになる。

□ インスタンス変数の値は、そのインスタンスが持っているメソッドの中からしか読み書きできない→カプセル化

インタフェース

4.5• カプセル化によってインスタンス変数群に決まった制約を持たせ続けることができ、外部からそれを破 □ 先に出て来た「見ていい部分」だけを定義として記述し壊されないことが言語仕様上保障されるたもの→オブジェクトの「外側から見える側面」→イン□ たとえば:タフェース (界面)「v = 複雑な計算 (x, y)」→ v の値を保存しておけば計算効率がよくなる→ただし x や y の値が• インタフェースを独立した記述対象とした言語→普及したものでは Java が最初変化したら必ず再計算• 「x や y を変更したら必ず再計算」を保証すること □ 例:ができるか?

// Javainterface Figure {public void draw(Graphics g);
public void moveTo(int x, int y);}changeX(...) { x = ○; recalcV(); }changeY(...) { y = ○; recalcV(); }useV() { return v; }relalcV() { v = 複雑な計算 (x, y); }□ 約束を変更したときにも変更が及ぶ範囲が限定される• 図は「画面に描ける」「X 座標/Y 座標と色を設定できる」• 「v が参照されないのに再計算は無駄」→この無駄を省けるか?
changeX(...) { x = ○; vChanged = true; }changeY(...) { y = ○; vChanged = true; }useV() { if(vChanged) {recalcV(); vChanged = false; }return v; }□ このような「メソッドの名前、引数/返値の型の情報を合わせたもの」を「シグニチャ(signature)」と呼ぶ。

• シグニチャ情報があればコンパイル時に型検査できる□ C++はインタフェースの代わりに「純粋な抽象クラス」
を使う□ その後のオブジェクト指向言語では、外部からインスタンス変数をアクセスできる「ようにも」指定可能に…(あまりよくないと思う)

4.4

Figure(図) というインタフェースを考える// C++
class Figure {public:virtual void draw(Graphics g) const = 0;virtual void moveTo(int x, int y) = 0;};

カプセル化の実現□ カプセル化は単なる「スコープの問題」だからコンパイ
ル時のみで処理• データ定義はない• virtual は「このメソッドは動的分配 (後述) を行う」
9

• 「= 0」は「このメソッドはこのクラスでは定義し □ インタフェース Figure は先に書いたもの (色も設定可
能にした)
ない」
□ 上のインタフェースを使ったグラフィクス (以下しばらくグラフィクスを使う関係上、Java のみになります)□ 本体クラスはパネル (窓の中にはまる部品)• 図形の配列 figs を持ち、中には円を 2 つ入れる
• 初期設定でマウスのクリックとドラグの動作を指定

interface Figure {public void draw(Graphics g);
public void moveTo(int x, int y);
public void setColor(Color c);}□ クラス Circle は円をあらわす(この書き方については後の方でやるのて当面こう書くと思ってください)• インスタンス変数は XY 座標、半径、色• コンストラクタでこれらを初期設定
class Circle implements Figure {int gx, gy, rad;Color col;public Circle(int x, int y, int r, Color c) {gx = x; gy = y; rad = r; col = c;}public void draw(Graphics g) {g.setColor(col);g.fillOval(gx-rad, gy-rad, rad*2, rad*2);}
public void moveTo(int x, int y) { gx=x;gy=y; }public void setColor(Color c) { col = c; }}• クリック時には count を増やす (配列最大を超えた
ら 0 に戻る)• ドラグ時には count 番目の図形をドラグ• repaint() は画面更新を指示• メソッド paint() は画面更新処理から呼ばれ自分の中身を描くことが期待されているメソッド
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Sample22 extends JPanel {Figure[] figs = {new Circle(50, 50, 30, Color.red),
4.6 動的分配
new Circle(80, 90, 50, Color.blue),};□ 変 数 x に オ ブ ジェク ト が 格 納 さ れ て い る と し て 、
int count = 0;x.m(...) はメソッド m を呼び出す。
public Sample22() {setOpaque(false);□ 変数 x がクラス A のインスタンスであれば、クラス A でaddMouseListener(new MouseAdapter() {定義されているメソッド m が呼ばれる。クラス B のイン
public void mousePressed(MouseEvent e) {スタンスであれば、クラス B で定義されているメソッド
count = ++count % figs.length;}m が呼ばれる。→どのメソッド m であるかは、実行時に});x に格納されているオブジェクトのクラスに応じて動的
addMouseMotionListener(new MouseMotionAdapter() {に定まる→動的分配 (dynamic dispatch)
public void mouseDragged(MouseEvent e) {figs[count].moveTo(e.getX(), e.getY());
repaint();• 「円の『描く』、矩形の『描く』、線分の『描く』は}すべて実装としては別のものだが、機能としては同});じに扱える」→ 1 つのコードで区別なく記述できる}
public void paint(Graphics g) {ようにしたもの
for(Figure f: figs) { f.draw(g); }• 動的分配がないと、「if 円 then 円. 描く () elif}
public static void main(String[] args) {矩形 then 矩形. 描く () else ... 」となってしJFrame app = new JFrame("Demo");
まう→コードがごちゃごちゃ、プログラマが一時に
app.add(new Sample22());
考えることが増える。これと対比すると、動的分配
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setSize(400, 300);
は極めて強力な機能だと言える
app.setVisible(true);}□ これからいくつか絵を描くので、Graphics オブジェク}

トが持つ図形描画メソッドを紹介しておきます。

□ main() はここから実行開始するところ• 窓 (JFrame) を作り、パネル Sample22 をはめ、×ボ
タンで閉じる設定をおこない、窓サイズを設定し、窓を開く

10

fillRect(x, y, w, h)

fillOval(x, y, w, h)

(x,y)

(x,y)

h

h
w

w

drawLine(x1,y1,x2,y2)

filPorygon(x, y, 3)(x[0],y[0])(x1,y1)(x2,y2)(x[1],y[1])(x[2],y[2])• void fillRect(int x, int y, int w, int h)--- 左上隅の XY 座標と幅と高さを指定して長方形を塗る• void fillOval(int x, int y, int w, int h)--- 楕円を塗る (楕円は上記の長方形の内接円として定義)• void drawLine(int x1, int y1, int x2, inty2) --- 線分を引く• void fillPolygon(int[] x, int[] y, int n)--- 多角形 (頂点数 n、各頂点の X 座標と Y 座標は配列に格納)□ では今日も練習問題をやってみましょう。Java で、先のクラス Circle の例題を参考に、次のクラスを作ってみてください。

• 練習 1:

正方形のクラス。作成時に中心の座標と 1

辺の長さを指定• 練習 2:長方形のクラス。作成時に中心の座標と幅と高さを指定• 練習 3:します

線分のクラス。作成時の指定方法はお任せ• 練習 4:三角形のクラス。作成時の指定方法はお任せしますclass Rect implements Figure {int gx, gy, width, height;
Color col;public Rect(int x, int y, int w, int h, Color c) {gx = x; gy = y; width = w; height = h; col = c;}public void draw(Graphics g) {g.setColor(col);g.fillRect(gx-width/2, gy-height/2, width, height);}
public void moveTo(int x, int y) { gx = x; gy = y; }public void setColor(Color c) { col = c; }}class Triangle implements Figure {int gx, gy, dx1, dy1, dx2, dy2;
Color col;public Triangle(int x0, int y0, int x1, int y1,
int x2, int y2, Color c) {gx = x0; gy = y0; dx1 = x1-x0; dy1 = y1-y0;
dx2 = x2-x0; dy2 = y2-y0; col = c;}public void draw(Graphics g) {int[] x = {gx,gx+dx1,gx+dx2}, y = {gy,gy+dy1,gy+dy2};
g.setColor(col); g.fillPolygon(x, y, 3);}public void moveTo(int x, int y) { gx=x;gy=y; }public void setColor(Color c) { col = c; }}class Flag implements Figure {Rect r1;
Circle c1;
public Flag(int x, int y, int r, Color c, Color d) {r1 = new Rect(x, y, r*4, r*3, c);
c1 = new Circle(x, y, r, d);}public void draw(Graphics g) {r1.draw(g); c1.draw(g);}public void moveTo(int x, int y) {r1.moveTo(x, y); c1.moveTo(x, y);}public void setColor(Color c) { c1.setColor(c); }}• 練習 5:

次のような図形のクラス Flag。中心の XY □ 強い型の言語の場合、変数 x に対してメソッド m が使え
座標と円の半径を指定るかどうかは型検査でチェックされる→変数 x に実行時に何が入れられるかを規定しておく必要 (先の例で出て4rr

来たインタフェースなど。型については後でとりあげる)
3r

(x,y)

4.7
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

動的分配の実現□ 最も原理的には…

• メソッドへのポインタの表 (メソッド表) を各オブ
public class Sample23 extends JPanel {Figure[] figs = {new Circle(50, 50, 30, Color.red),
new Circle(80, 90, 50, Color.blue),
new Rect(200, 120, 80, 60, Color.yellow),
new Triangle(120, 60, 160, 60, 140, 20,
Color.green),
new Flag(240, 200, 30, Color.white, Color.red),};// 途中略…

ジェクトに持たせる• この表を検索してメソッドを見つけてから呼び出す

11

Circle instance

xpos

ypos

ypos

rad

len

col

col

draw

methodmoveTogetX
getY

class A

Square instance

xpos

draw

int x
int y
metod A { ... }metod B { ... }methodmoveTomethodmethodgetX
getY

methodclass Bextends A

methodmethod

int x
int y
int z
metod A { ... }metod B { ... }metod C { ... }method□ 実際にはいくつかの点で最適化• 同じクラスのオブジェクトならメソッドは同じ→メ
ソッド表はクラスごとに 1 つだけ作成し、各オブジェ• 典型的には、インスタンス変数定義とメソッド定義(実現の継承)クトは自分が属するクラスを表す情報 (クラスオブ
ジェクトへのポインタ) を持つ• 追加や差し替え (オーバライド) も可能• 一定の条件が整えば表を探索しなくても「表の何番• しかし厳密な「継承の定義」をしはじめると難しい

目か」はコンパイル時に分かる(後述)• 表の探索が必要な場合も、一度探索した結果をキャッ
シュしておくと次回はそれが再利用できることが多い□ Java では継承もインタフェースと同様、動的分配の対象になる (むしろ一般的には継承の方が先)□ C++では「virtual」指定の関数が 1 個以上ある場合にのみ、メソッド表が生成され、各オブジェクトにそこへ□ C++では継承しても親クラスで virtual と指定していなのポインタが追加されるいメソッドは動的分配しない• virtual が 1 個 も な い ク ラ ス →「 具 象 ク ラ ス □ 継承は何が嬉しいか?
(concrete class) →単純な ADT として使い、効率• 類似したクラス群を少ない記述で作成できる、定義
がよいの共有• virtual を持つクラス→動的分配によるオブジェク
ト指向的なプログラミングのためのもの• 新しいクラスを作るとき、違うところだけ書けばよい→差分プログラミング□ ただし、そのようなことをやる場合は「ポインタ型を」• 共通の親を持つクラスのオブジェクトを総称的に扱える (なぜなら同じ変数群、同じメソッド群を持つ使う必要がある。なぜか分かりますか?
□ (ポインタでなく) 直接値を扱うと→その値の領域サイ
ズはコンパイル時に決定されて確保される→そこに「さまざまなデータを」入れることはできない (入り切らなから)(ただしそのためには動的分配も必要)• 強い型のオブジェクト指向言語では、子クラスの値
は親クラスの型に互換→型の問題 (後述)

い部分は切捨てられてしまう!!!)□ たとえば先の例で Circle と Square の違うところは「ほんの少し」→その「差分」だけ書いてすませることもできる。
• …ということは、オブジェクト指向ぽく使うばあいはどのみちポインタにするしかない→そうなると Javaの「全部ポインタで統一」が魅力的に見える

4.8

継承

□ 継承とは→あるクラスから別のクラスに定義を「引き継ぐ」ことclass Circle implements Figure {int gx, gy, rad; Color col;public Circle(int x, int y, int r, Color c) {gx = x; gy = y; rad = r; col = c;}public void draw(Graphics g) {g.setColor(col);g.fillOval(gx-rad, gy-rad, rad*2, rad*2);}
public void moveTo(int x, int y) { gx=x;gy=y; }public void setColor(Color c) { col = c; }}class Square extends Circle {int len;12• 純粋な抽象クラスの継承→ Java でいう implementsと同様の役割りpublic Square(int x, int y, int l, Color c) {super(x, y, 0, c); len = l/2;}
public void draw(Graphics g) {g.setColor(col);g.fillRect(gx-len, gy-len, len*2, len*2);}• 継承は複数指定可能→多重継承 (後述)
• コンストラクタの冒頭で親クラスのコンストラクタ
を呼ぶ構文がある (メンバ変数も同じ構文で初期化できる…初期化と代入が違うので結構重要)。

}□ この「super(...)」とは?

class Someclass : public Parent1 {double val;public:Someclass(...): Parent1(...), val(3.14) { ... }}→ 親クラスのコンストラ

クタを呼ぶ。

• コンストラクタを呼ばないと初期設定が行なわれないので危ないから、呼ばないと許されないことになっ
ている。

4.9

• 引数なしのコンストラクタがあれば、自動でそれを呼んでくれる

抽象クラス□ 抽象クラス:機能の一部を子クラスの実装に任せるよ

うなクラス• クラスにコンストラクタを書かなければ、引数なしのコンストラクタが自動的に作られる• その「子クラスに任されている」メソッドを抽象メ
ソッドという• →全体として、初期設定を必ず通る方向でチェックされる• Smalltalk では、抽象メソッドは例外を投げることで示す• メソッドの方でも、オーバライドした子のメソッドの中から「super. メソッド名 (…)」で親のメソッドを呼び出せる□ Java では抽象クラス/抽象メソッドを宣言→コンパイラが検査 (冒頭に修飾子「abstract」) をつける

□ それはそうと、上のような「差分プログラミング」はど□ C++では個別のメソッドに「= 0」を指定するだけでクラう思う?

スについては特に指定はない□ 先の例題で「円と長方形の共通部分」を SimpleFigureという抽象クラスにくくり出してみる□ 先の例題も継承を使った実例になっている• Sample22 等は JPanel からメソッド群を継承• 自分の領域を再描画するときはメソッド paint() を呼ぶ• JPanel のサブクラスで paint() を差し替えることで独自の画面を作成□ このように「ある決まった部分だけオーバライドにより差し替えて使えるような構造」→「アプリケーションフ
レームワーク」• 実際にはもっと大きいプログラムで使われる (例:MFC)□ C++における継承→グラフィクスの例題は無いので、概要だけ。

• 親クラスは次の形で指定する
class クラス名 : public 親クラス {...
}• public の代わりに private とも指定できる→継承
したものはそのクラス内でしか参照できない。つまり外から見たら継承していないのと同じ (純粋な実
装の継承)

13

abstract class SimpleFigure implements Figure {int gx, gy;
Color col;public SimpleFigure(int x, int y, Color c) {gx = x; gy = y; col = c;}public abstract void draw(Graphics g);
public void moveTo(int x, int y) { gx = x; gy = y; }public void setColor(Color c) { col = c; }}class Circle extends SimpleFigure {int rad;public Circle(int x, int y, int r, Color c) {super(x, y, c); rad = r;}public void draw(Graphics g) {g.setColor(col);g.fillOval(gx-rad, gy-rad, rad*2, rad*2);}}
class Rect extends SimpleFigure {int width, height;
public Rect(int x, int y, int w, int h, Color c) {super(x, y, c); width = w; height = h;}public void draw(Graphics g) {g.setColor(col);g.fillRect(gx-width/2, gy-height/2, width, height);}}

class Triangle extends SimpleFigure {4.11 本節のまとめ
int dx1, dy1, dx2, dy2;
public Triangle(int x0, int y0, int x1, int y1,□ オブジェクト指向言語の基本部分→ Simula にほとんど
int x2, int y2, Color c) {ある (一部ないものもある)
super(x0, y0, c); dx1 = x1-x0; dy1 = y1-y0;
dx2 = x2-x0; dy2 = y2-y0;• クラス、インスタンス}
public void draw(Graphics g) {• コンストラクタ→初期設定
int[] x = {gx,gx+dx1,gx+dx2}, y = {gy,gy+dy1,gy+dy2};• インタフェース→オブジェクトの「切り口」「使い方」
g.setColor(col); g.fillPolygon(x, y, 3);}• 動的分配→オブジェクト指向言語の特に重要な機能}• 継承→差分プログラミング、フレームワーク、(インclass Flag extends Circle {Rect r1;
タフェースを兼ねる)
public Flag(int x, int y, int r, Color c, Color d) {super(x, y, r, d); r1 = new Rect(x, y, r*4, r*3, c); • 抽象クラス→クラス階層の構造化}□ ここまでで課題の 1 番目はできるように配慮しました。
public void draw(Graphics g) {r1.draw(g); super.draw(g);}public void moveTo(int x, int y) {5 Smalltalk: オブジェクト指向の再発
r1.moveTo(x, y); super.moveTo(x, y);}見}□ Smalltalk システム:• 確かに共通部分がくくり出されて簡潔になっているがAlto システム上の言語処理系+

実行環境

…

• Flag については悩ましい• Alto: ゼロックス社の Palo Alto 研究所で開発された「世界最初の」「パーソナルワークステーション」• 「円の子供」にしたわけは、setColor() で円の色が変更されるため• Smalltalk にはいくつかバージョンがあるが、1980• 本来は円と長方形が平等だったのに気持ちがわるい

年の Smalltalk-80 が一応完成された版→その後も• 円と長方形をそれぞれ持ったままにしてもいいが、そ少しずつ改良うすると今度は自分のインスタンス変数はどれも使• Alan Key らが Dynabook 構想の実現の一歩として開発した

わないことになるので…

• ビットマップディスプレイ、対話的グラフィクス、サ

4.10

クラス階層の設計

ウンド、ウィンドウシステムなど、当時の水準から見ると極めて先進的なシステム□ (抽象クラスや継承を含めた) よいクラス階層のデザイ□ オブジェクト指向によるプログラミングの容易さがあっ

ン→なかなか難しい重要な部分てはじめて可能だった、とされている□ 指針としては「抽象的なもの (上位概念) →親クラス」□ 「再発見」という意味→ Simula にはじまるオブジェクと言われているが…
□ 「円」と「楕円」はどっちが親クラス?

ト指向言語について、世の中に再認識させた

またはどちらでもない?

• プログラミング言語屋にとっては「センセーション」
だった• 円は楕円の特殊な場合である• 円よりも楕円の方が (長径と短径があるので) 機能は多い□ Smalltalk の「見ため」の多くは Apple の Lisa →Macintosh に引き継がれた (Smalltak 言語は引き継がれなかった)□ 「円」が使われるところすべてに「楕円」をあてはめてよいか?

それが YES でないようなアプリケーションな□ Smalltalk 言語は製品としてずっと存在し続けたが開発ら、独立したクラスにした方がよい。

言語としてはマイナーであり続けた。有償だったし。

• YES であれば、「円」に「縦横比率」を追加したもの □ 1997 年、オリジナル Smalltalk の開発者たちが再が楕円、という位置付けで楕円をサブクラスにする
結集して開発したフリーの処理系 Squeak が公開にことはあっていいかも(http://www.squeak.org/)• 結局、アプリケーション (やライブラリの使用想定
場面) 次第14

5.1「純粋な」オブジェクト指向言語

5.4□ 制御構造もブロックとメソッドで構成□ 「純粋」という意味→すべてがオブジェクトである。たとえば整数や文字や論理値も (C++、Java 等ではこれら(x > 10) ifTrue: [x ← x - 1] ifFalse: [ ... ].
[x > 10] whileTrue: [....].

は基本型でありオブジェクトではない)• そのため、極めて統一的な言語仕様とできる (すべ• なぜ上は「(...)」で下は「[...]」なのか分かりま
すか?

てのもののふるまいはサブクラスを作って改良可能)• たとえば「整数」のふるまいを変えたものも作れる□ そのほか「このような値が見つかるまで探す」といった• ただしリテラルがもとの Integer クラスのものな

指定にもブロックを利用→ Lisp 系の言語に近い (記号

ので…

型もある)

• コンパイラを変更してしまえば変えられる

5.2

制御構造

5.5

特徴的な構文

先進的なプログラミング環境□ ウィンドウシステムがほとんど普及していない時期から□ すべては「メッセージ送信式」(と変数代入)

ウィンドウ環境だった• キーワードセレクタ型:□ クラスブラウザ、バックトレーサ、デバッガなどが組み

オブジェクト セレクタ.
valu ← aPoint x.
オブジェクト セレクタ: 引数 セレクタ: 引数 ….
anArray at: 10 put: x.

込まれた統合プログラミング環境だった• ソースを追加/修正すると環境全体が変化していしまう→環境全体のダンプを取って保存 (もちろんソー• 演算子セレクタ型:

ス単独でも保存とロードはできたが)オブジェクト 演算子 オブジェクト.
x ← x + 1.

• 全体的に言語、環境とも Lispっぽいと言える。cf.
InterLisp-D□ 「メッセージ送信」とは要するにメソッド呼び出しのこと• オブジェクトが指定されたセレクタに対応するメソッドを持たないときは、messagenotunderstood とい

5.6

MVC フレームワーク

うセレクタのメッセージに変換されて送り直される □ 画面に見える「もの」(ウィンドウの内容や部品) を M/V/Cに分けて構築(このメソッドは Object クラスで定義されている)→自前でエラー処理したければこれをオーバライド• 並列性や分散性は Smalltalk にはない (注:• Model:

スレッ「もの」の状態を表すオブジェクト。たとえばスライドレバーであれば「現在の値」

ドはあるが並列実行ではない)。この面で拡張を行う• View: 「もの」の状態を表示するオブジェクト。た

研究は多数あり → Concurrent Smalltak、その他

とえばスライドレバーであれば、「レバーの絵」や「数値表示窓」

5.3

コードブロックの多用□ コードブロック:• Controller:「もの」を操作するための動作を提供するオブジェクト。たとえばスライドレバーであ

コードの断片だが、それ自身オブジェ

れば「レバーをドラグする」「プラス/マイナス押し

クト。他の言語でいえば「クロージャ」に相当するボタン」。
• メッセージ valu(引数つきの場合は「value: 引数」
等) を送ると、そのコード内容を実行して return 文 □ 1 つのモデルに対してビュー、コントローラは複数あっで指定した値を返すてよい (上の例)
z ← [x ← x + 1. ↑ x] value.
z ← [:n | x ← x + n. ↑ x] value: 100.

□ その後の多くのグラフィカルなシステムにおいて MVC フ
レームワークが採用された• ブロックはブロックの周囲の環境をアクセスできる(だからクロージャ)• 一方で、副作用だらけになるという問題点も• View と Controller を分離する必要はどれくらいあるか? Java 2(Swing) などではこれらを一体化した delegate というものを使用□ Java では、コードブロックは無いがその代り「内部クラス」や「無名の内部クラス」が使える (後述)15

• モデルを分離する、という考え方はいずれにせよとても有効 (後述)5.7

本節のまとめ□ 実際には、「通常の (インスタンスを作る) クラス」と、「他のクラスにまぜて機能を追加するクラス」(mixin ク□ Smalltalk → オブジェクト指向の強力さを世に知らしラス) を区別して使い分けることが通例めた• 例: ウィンドウクラスに対し、「窓枠をつける mixin
クラス」などを混ぜて機能の増えたウィンドウを作っ• 「純粋な」オブジェクト指向言語 (整数等もオブジェ
クト)

て行く

• コードの集まり (ブロック) もオブジェクト• このような操作を mixin 操作と呼ぶ• 制御構造はブロック等のメソッド□ MVC フレームワーク→パターンの元祖

6.3□ Flavors で提案されたもう 1 つの重要な拡張。

Lisp 系のオブジェクト指向言語

6

メソッド結合• Smalltalk では子クラスのメソッドは親クラスの同□ Smalltalk は最初から Lisp によく似た側面を備えて

名メソッドを置き換え→親クラスのメソッドの動作いた「も」利用したい場合は「super セレクタ …」により明示的に呼び出し• そのため、Lisp 屋は Lisp にオブジェクト指向を導入することで Smalltalk のようなよい言語/環境を• 多重継承では親が複数あるから上の方法ではいまいち入手できるのではと考えた• C++では「どの親の同名メソッド」という形で呼べるが、この方法で十分かどうかは?

• 実際、多くの Lisp ベースのオブジェクト指向言語が作られた□ Flavors では、通常のメソッド (primary) のほかに、• その際、Smalltalk や Simula になかった新しい概念も多く考案された

deamon メソッド (before daemon、after daemon) がある (実際にはもっとあるがこれらが主に使われる)□ 現在でも標準として残っているのは CLOS(Common Lisp □ 多重継承とメソッドのオーバライドがある状態では…、次の順でメソッド群が呼ばれる
Object System) →ただし今後の Lisp はどれもオブジェ
クト指向機能は持つようになる (CLOS 方式かどうかは• まず、before daemon が親クラスから子クラスへの分からないが)6.1

順で呼ばれる• primary method はこれまで通りのオーバライドなので一番最近に定義された子クラスのものだけが呼Flavors

ばれる□ Zetalisp (Lisp Machine System で採用した Lisp の• 最後に after daemon が子クラスから親クラスへの方言) 上のオブジェクト指向機能。クラスのことを順で呼ばれるflavor と呼ぶ• (defflavor フレーバ名 各種情報…) で「クラス」□ 何のためにこうなっている?

しまつ」、after daemon は「後しまつ」を行い、それらを定義は順番に結合されて各レベルのクラスの仕事を実行する• flavor のインスタンス→オブジェクト□ 使いこなせば便利なのかも知れないが、やっぱり難しい• (send オブジェクト セレクタ 引数…) → メッセー

(と思う)

ジ送信
□ ここまでのところは Smalltalk と本質的におなじ

6.2

6.4

CLOS とマルチメソッド方式□ CLOS (Common Lisp Object System) → CommonLisp

多重継承の言語仕様のうちの、オブジェクト指向機能部分をいう□ Flavors による重要な拡張の 1 つ。flavor には複数の
親 flavor が指定できる→多重継承→ before daemon は「前(後から追加されたもの)□ 最大の変化→汎用関数 (generic function) に基づく
メソッド呼び出し• 多重継承では小クラスは親クラスすべてからインス
タンス変数、メソッド群を引き継ぐ→「混ぜる」こ
とによる干渉もあり使い方は難しい

16• Flavors: (send オブジェクト セレクタ …) → 7.2 弱い型のオブジェクト指向言語「どのメソッドか」は「オブジェクト」と「セレクタ」□ 変数にも式にもコンパイル時の型はないで決まっていた• 最初の引数 (レシーバ) のみを重視しすぎ? → 「す □ しかし、実行時には型 (==クラス) がある
べての引数がメソッドの決定に関与する」• 「anObject message.」→「OK である」か「そのメ(defclass X ....)(defclass Y ....)(defmethod method1 ((a X) (b Y)) ... *1 )(defmethod method1 ((a X) (b X)) ... *2 )...
(method1 anX anY) → *1 が呼ばれる(method1 anX anX) → *2 が呼ばれる

ソッドはない!」かどちらか。

• 「そのメソッドはない」がどこで起こり得るかを予め知る方法はない→製品としてソフトを作るときには弱点となり得る• 「OK である」ならよいのか? → たまたまそういう名前のセレクタが利用可能、だったらもっとたちが• 「メソッドがクラスに付属していない」「構文的には普通の関数みたいな見え方」→特徴的 (好みも分か

悪い?

れる)

• 多重継承やメソッド結合は Flavors 以来引き継がれ

7.3

ている

強い型のオブジェクト指向言語□ Simula が既にそうであった

6.5

本節のまとめ• 「型」と「クラス」は同じものとみなす (ちょっとは違うが)□ Lisp 系のオブジェクト指向言語→メジャーではないが• 「ある型の変数/式」は実行時に「そのサブクラスの値も持つことができる」

様々な試み• 多重継承□ この規則は通常の「強い型の言語」からはだいぶ離れている• メソッド結合• メタオブジェクトプロトコル (MOP)

Pascal ... x:integer := 1;x の型:integer、1 の型:integer
Java ... o:Object := new Integer(1);o の型:Object、式の型: Integer(Object のサブクラス)• マルチメソッド

オブジェクト指向と型

7• Object 型には任意のオブジェクトが入れられてしまう□ Simula は強い型の言語だったが、その後、Smalltalk、

Flavors、等はすべて弱い型の言語• 代入の左辺と右辺の型は同じでなく包含関係→複雑□ C 言 語 に オ ブ ジェク ト 指 向 を → や は り「 オ ブ ジェク

さの原因• ただし、メソッドを呼ぶときは型が合わないとだめ

ト型」はすべて一緒、というタイプが多かった (例:Objective-C) →強い型ではない

int i = o.intValue(); ... ×
int i = ((Integer)o).intValue(); ... ○□ 10 年以上たって、ようやく「強い型のオブジェクト指向言語」が当たり前になった (C++が代表的)• このキャストは「実行時の型検査を伴うキャスト (ダウンキャスト)」であって C のキャストとは違う (も7.1

ともとのオブジェクトが Integer ないしそのサブク

強い型の概念と利点/弱点□ 強い型とは?

→ コンパイル時にすべての式や変数の型が定まっている• 利点:

コンパイル時検査、設計の手段• 弱点:

繁雑、めんどくさい???

□ 中庸もある:

ラスでなければ例外が発生)

7.4

型情報を利用した自動型変換□ すべての式や変数に型がある→それらに「不一致」があることも

例 CommonLisp → 型はなくてもいいけ• 数値型など基本型の間では一定範囲で「自動変換」がど、指定してもいい。指定すると効率がよいかも/コン

提供されている

パイル時検査が可能• オブジェクト型どうしでは「上位の変数に下位のオブジェクトが入れられる」という規則が「原則」
17• オブジェクトと基本型→相互変換行わないのが「原則」//x = 20;cout << x.value() << "\n";//cout << (x+0) << "\n";}• 便利さのためのこの「原則」をやめて変換を可能とする場合がある□ Java の AutoBoxing/Unboxing• Java では「1」(値) と「new Integer(1)」のよう

7.5

実行時の型情報に基本型とそれに対応するオブジェクト型の両方を □ 前記のキャストのようなことができる←実行時にもある
用意→相互に変換することが多い
程度の型情報が保持されている• Boxing --- 「箱に入れる」: 値をオブジェクトに □ Java の場合: 上記のダウンキャスト、およびある型に属するかどうかを判定する instanceof 演算子
する• Unboxing --- 「箱から出す」: オブジェクトから
if(o instanceof Integer) ...
値を取り出すInteger i1 = new Integer(1); ← boxing
int j1 = i1.intValue();← unboxing• もっと自由に型の情報そのものを扱う→自己反映機

能 (後述)• JDK 1.5 以 降 で こ れ ら を「 自 動 化 」:boxing /unboxing が必要になったときは自動的に行う (演算 □ C++の場合: 実行時の型情報は virtual メソッドのあなどの場合も)るクラスに対してのみ利用可能。そうでない場合は実行
Integer i1 = 100, i2 = 200; ← auto boxing
時には情報がない。
int j1 = i1, j2 = i1+i2; ← auto unboxing
int j3 = i2++;← auto unboxing+boxing• チェ ック の あ る ダ ウ ン キャス ト →• しかし「==」「!=」はオブジェクトどうしが等しいという意味がもともとあるので auto unbox されない

dynamic cast<T>(値)• 実 行 時 に 情 報 が な い 場 合 の キャス ト →static cast<T>(値)ことに注意• メソッド呼び出しのために autoboxing されること• const の あ る 型 か ら
const cast<T>(値)はない。「3.toString()」はダメ

を 外 す →• 何 を 何 に で も 変 換 で き る キャス ト →□ C++の自動変換機能

reinterpret cast<T>(値) ← こ れ ま で の「(型)値」というキャストと同等。これまでのキャストは• 多くの場合「変換」しなくても演算子オーバロード
で済む

危険な上に目立たないのでやめさせたいという考え• コンストラクタを定義することで自動型変換が可能になる• 代入時も別扱いしたければ代入演算子も定義

const

7.6

インタフェースと型• 基本型や既にあるクラスを「変換先」にしたい…コ □ 単一継承でサブクラス階層だけだと型の包含関係は非常
ンストラクタは定義できない→その場合は「型変換にシンプル演算子」を定義 (「operator T()」…自分クラスの□ 多重継承があると 1 つのインスタンスが複数の親の型に値を T 型に変換)含まれることになるが、まだ包含関係は明らか• これらの複数の機能のどれを使うか→言語仕様で規定 (複雑)□ インタフェースではそれを実装しているすべての型を「くくる」という点で不規則な構造が作れてしまう (た#include <iostream>using namespace std;
class Test {int val;public:Test() { val = 9; }//Test(int i) { val = i+1; }//void operator=(const int& i) { val = i+2; }//operator int() const { return val+3; }int value() { return val; }};int main() {Test x;//Test x = 10;

だしループは許さない)• インタフェース自体にも包含関係があるのでさらに面倒なことに…

7.7

本節のまとめ□ 強い型はコンパイラによる誤り検査や設計の手段として有用□ 「お仕事の言語」では強い型のものが主流

18

□ オブジェクト指向以前の「強い型」とオブジェクト指向uclass U

x

int x
int y

の「強い型」はやや違っている (型の包含関係)• より広い型の変数に狭い型の値 (インスタンスを入

metod A { ... }metod B { ... }

y

れられる)

w

• 広い型から狭い型に戻すことができる (実行時チェッ
クが必要)

class W

x

int x
int y
int t
metod A { ... }metod B { ... }metod D { ... }

y

継承と委譲

8

t
v

□ 継承 (inheritance) → Simula、Smalltalk 以来の「由緒正しい」やりかた

class V
int x
int y
int z
metod A { ... }metod B { ... }metod C { ... }x• 使っているうちに、色々問題もあることが分かって来た

yz□ 委譲 (delegation) →継承の代替として使えるメカニ• この方法であれば、クラス A のどのサブクラスでもズム

A までで定義されている変数のオフセットは同一 →オフセットで直接アクセス可能• 複数オブジェクトの組合せ (composition) と相性がよい→継承よりも委譲を使うことが増えている

8.1• メソッドのコードがそのまま利用可能• 分かりやすく、効率がよい。ただし多重継承に対応できない

継承の意味づけ□ 継承がやってることは何かというと…

8.3• インスタンス変数とメソッドを引き継ぐ

メソッド探索• その結果として、呼べるメソッドの集合や外から見 □ メソッド呼び出しで実際にどのメソッドが動くかはオブジェクトのクラスによって変化→メソッド探索たオブジェクトの振る舞いを引き継ぐ• たとえば B が A のサブクラスであれば、「A のインスタンス」の代りに「B のインスタンス」を与えてもそ• Smalltalk では、最初にそういう呼び出しがあった
ときに探索を行い、その情報をキャッシュに保持。ク

のまま動く (建前としては)ラス構造が変化したときはキャッシュをご破算にして• 動的分配の前提として、「A 型の変数に A のサブクラ
スがいろいろ入れられる」ということが必要

やりなおす。動的にクラスが変化する環境ならでは• C++、Java などのコンパイルする言語では、メソッド表を作ってそれに基づいて分岐すればよい。表そ□ しかしよく考えると、これは「実装の継承」が先にあり、のもののスロットも変数と同様にして管理可能

その結果として「たまたまどれでも同じように取り扱え• インタフェースの場合は 1 つのクラスがさまざまな
インタフェースを実装しているので面倒。Java ではる」ようになっているだけとも思える。この「たまたま」は気持ち悪い呼び出し時に探索しているが、工夫次第では「表引き」にもできる

8.2

継承の実装

□ ごく素直な継承の実装方法→オブジェクトの構造が重要。 8.4
インスタンス変数を定義された順に並べておく→子クラスでインスタンス変数を追加した場合は後ろにつけ加えていく

多重継承の実装□ 多重継承→ C++ではサポート。Java では単純化のため
禁止。
□ 問題:

複数の親が共通の親クラスを持っていたらどうするか?

• 案 1:

親のインスタンスが 2 個埋め込まれる→その場所は親クラスとしてそのまま扱える (ただしポインタ逆変換の問題がある)(C++)

19• 案 2: 共通部分は「統合」される→「どこに親が埋まっているか」のポインタをそれぞれのインスタン• 実行時に機能を追加したり外したりといったことはできない。

スに埋める必要 (C++)• しかし場合によっては実行時に機能の調整を行ないたい。たとえば「動かない円」を作っておいたがそ• 案 2 の別解→最初に名前で探索し、その場所を覚える (Flavors)

れを途中で動くようにしたい等。

□ C++では案 2 は「virtual 親クラス」と呼ばれている。 □ 例:
両方あるのは C++らしいが複雑。

GUI 部品と動作• たとえば「ボタン部品」を考えてみる。ボタンには• とくに、コンストラクタによる初期設定が複雑。
「子」
が共通の「親」を初期設定すると 2 回初期設定され「ボタンを押した時の動作」があるはず。その動作はてしまう。このため、virtual 親クラスになるもの• しかし汎用の Button クラスには当然、アプリケー

アプリケーション固有には「引数無しの」ものが用意されている必要。こション固有の動作は入っていない

れが最初に呼ばれる。
「子」のコンストラクタでは親• ではどうするか?

のコンストラクタは呼ばないのが通例。

→ 継承を使って、「動作」メソッ

ドをオーバライドして、そのメソッド中でアプリケー
ション固有の動作を行なわせる

8.5• Java は JDK 1.0.x ではそうしていたが、JDK 1.1

継承の問題点からはやめている→動作 1 つごとにサブクラスを作□ 継承にはさまざまな問題点があった→何が問題か、分かるのが煩雑、複数の場所 (例:ります?

メニュー項目やキー

ボードショートカット) から同じ動作を起動するときの共有ができない、実行時に割り当てを変更でき□ 実装と界面の混同ない• 継承は実装を引き継ぐことで、インタフェースを共通にすること、多相性 (polymorphism、動的分配)を提供することはまた別の問題だが混同されがち

委譲 (delegation)

8.6• Java のようにインタフェースを別にすることが必要□ 継承は「親のインスタンス変数やコードを取り込んで来(ただし Java でも継承すると多相性がついてくるのて自分の一部として実行させる」→カプセル化が壊れる
で中途半端)
等の問題□ カプセル化の破壊□ 別オブジェクトの機能が必要なら、それを別のインスタンスとして持っていて、これを普通に呼び出すことでも• サブクラスを作ると、その中では親クラスの変数が自由にいじれてしまう→カプセル化の破壊。

利用可能→委譲の考え方• C++、Java など→サブクラスでいじれる変数、いじ• 簡単に言えば、自分で実装しないメッセージを「たれない変数を区別可能に (private →サブクラスでもいじれない変数、protected →サブクラスでもいじれる変数) →それが問題の解決になってるのかど

らいまわし」にする□ 委譲のさまざまな利点

うか??

• カプセル化が壊れない• 委譲先を実行時に動的に切り替えることができる□ 機能追加の制約• 多重継承の実装が容易 (多重継承の実装として、一• 単一継承の場合、1 つずつしか機能を追加して行けない→たとえば図形に「動く機能」「大きさが変わる部に委譲を使うことも)機能」を追加したいとすると、それぞれの機能のあ □ 例:
るなしごとにクラスを分けることに→機能が N 種類

GUI 部品の「ボタン」に動作をつける場合• JDK 1.1 以降では各ボタンは次のメソッドを持つあったら 2 の N 乗必要→組合せ爆発

public void addActionListener(ActionListener l)• 多重継承があれば「動く機能」「大きさが変わる機能」などをそれぞれ別のクラスにして「混ぜる」ことで必要なクラスが作れる。ただし混ぜたものの干渉が心配□ 継承は静的

20• ActionListener は次のようなインタフェース
public class ActionListener {public void actionPerformed(ActionEvent e);}• 各ボタンの動作は ActionListener インタフェース □ クラスがなく、「ひな型」のオブジェクトを複数コピーすることでインスタンスを作るを実装するクラスのインスタンス (アダプタオブジェクト) のメソッド actionPerformed() として用意• 委譲した場合でも「元のオブジェクト (プロトタイプ)」を覚えておいて、自分自身に対してメッセージを送った場合元から探す。これがないと次のようなメソッド (抽象メソッド) が使えない• ボタンは押されるとアダプタオブジェクトの上記メ
ソッドを呼び出す□ 例題:

華氏摂氏換算moveRight | n |self turn 90.
self forward n.

• 今度は GUI 部品だけ使うので、paint() はなく、コンストラクタで GUI 部品の位置設定□ Self 言語は「プロトタイプ方式の」「委譲に基づく」オブ
ジェクト指向言語が有用だという実証の意味が大。コーimport java.awt.*;
import java.awt.event.*;
import javax.swing.*;

ドの性能も動的コンパイル (よく使う/高速な組合せだ
けをコンパイルしていく) などの技術により優れていた

public class Sample25 extends JPanel {□ もう 1 つの (極めて普及している) プロトタイプ方式のオ
JLabel l1 = new JLabel("F-C converter."); //←ラベル
ブジェクト指向言語→ JavaScript(次回に取り上げる)
JTextField f1 = new JTextField();//←入力欄JButton b1 = new JButton("Calc");//←ボタン
public Sample25() {setLayout(null);//←自動配置機能 OFF
8.8 本節のまとめ
add(l1); l1.setBounds(20, 40, 200, 30); //←位置
add(f1); f1.setBounds(20, 80, 120, 30); //←設定□ 継承はオブジェクト指向言語の大きな特徴として注目
add(b1); b1.setBounds(160, 80, 60, 30); //←b1.addActionListener(new MyAdapter1(this));• もっとも「継承のないオブジェクト指向言語」も可}能だしあるがpublic String getField() { return f1.getText(); }public void setLabel(String s) { l1.setText(s); }public static void main(String[] args) {□ 委譲 (たらいまわし) は最初は「単なる別のオブジェク
JFrame app = new JFrame("Demo");
トの呼び出し」だったが、次第に継承に代わる機構としapp.add(new Sample25());て認識
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setSize(400, 300);□ オブジェクト指向プログラミング全体が継承指向から委
app.setVisible(true);
譲指向に変化}}class MyAdapter1 implements ActionListener {Sample25 app;
9 Java の入れ子クラスと内部クラス
public MyAdapter1(Sample25 a) { app = a; }public void actionPerformed(ActionEvent e) {□ 入れ子クラスについて説明
double f = Double.parseDouble(app.getField());
app.setLabel(f + "F = " + (5.0/9.0*(f-32)) + "C");• 入れ子クラスの特別な場合である「内部クラス」に}}ついても説明• 先の例題でどのようにしてマウス入力を取っていた□ アダプタオブジェクトは「オブジェクト」なので、そのかがここまで読むと分かる

中に「呼ばれた」時に必要なデータを持っておくことができる• ここでは、Sample25 オブジェクトを持っておき、呼9.1

入れ子クラスび出されたらそのメソッドを呼び出して入力欄の内 □ クラスの中に別のクラスを書けるようにしたオブジェク
容を読み出したり出力ラベルの値を設定している
ト指向言語はまだあまり多くない。

8.7• クラスはモジュールのようなもので、ある程度の完

Self: プロトタイプ方式のオブジェクト指向言語

結性があるから、あるクラスの中に別のクラスを入れると言うことはあんまり必要がないと思っていた?

□ ここまでは「委譲」をコーディング上の手法として考え □ しかし Java ではクラスの中に「そのクラスだけが使うて来たが、言語機構として継承の代わりに委譲のみを使下請けクラス」が書ける→入れ子クラス
う言語も→ Self

21• これは、Java が「必要があればどんどんクラスを作って使う」方向だから← Smalltalk などもそういう文

import javax.swing.*;
public class Sample26 extends JPanel {JLabel l1 = new JLabel("F to C converter.");JTextField f1 = new JTextField();
JButton b1 = new JButton("Calc");
public Sample26() {setLayout(null);add(l1); l1.setBounds(20, 40, 200, 30);add(f1); f1.setBounds(20, 80, 120, 30);add(b1); b1.setBounds(160, 80, 60, 30);
b1.addActionListener(new MyAdapter1());}
public String getField() { return f1.getText(); }public void setLabel(String s) { l1.setText(s); }public static void main(String[] args) {JFrame app = new JFrame("Demo");app.add(new Sample26());
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setSize(400, 300);
app.setVisible(true);}class MyAdapter1 implements ActionListener {public void actionPerformed(ActionEvent e) {double f = Double.parseDouble(f1.getText());
l1.setText(f + "F = " + (5.0/9.0*(f-32)) + "C");}}}化だが、クラスだらけでごちゃごちゃになりがち• Java ではパッケージ (階層的な名前を持つ) →クラス→入れ子クラスという 3 段階だからだいぶいい。
入れ子クラスの中にさらにクラスを入れることもできる (まず見かけないが)9.2

Java の内部クラス□ インタフェースを使ってアダプタクラスを作るような場合:• 小さいクラスが多数できてしまうので面倒→これに対しては入れ子クラスを使えばよい• アダプタクラスが保持するデータはインスタンス変数として保持する必要→その宣言や初期設定等が結構面倒• そこでどう考えたか…
□ 2 種類のメソッド• static のついたメソッド (クラスメソッド) →イン

スタンス変数にはアクセスできない、独立した関数 □ 外側クラスのインスタンス変数に直接アクセス→そのためコンストラクタも変数も不要→さっきよりずっとコンのようなものパクトに• static のつかないメソッド (インスタンスメソッド)→インスタンスに付随していて、インスタンス変数
を読み書きでき、他のインスタンスメソッドをそのまま呼び出せる

9.3

無名クラス

□ 上の例では MyAdapter1 等の名前を使う個所はそれぞれ□ これにならって、入れ子クラスも 2 種類にする!

1 個所ずつしかない• static のついた入れ子クラス→インスタンス変数に □ 1 個所でしか参照しないようなアダプタクラスの名前をはアクセスできない、独立したクラスと同様
いちいち考えるのは嬉しくない• static のつかない入れ子クラス→*外側の*クラスの □ このような場合には、なおかつ「そのクラスが 1 つのク
インスタンス変数にアクセスでき、外側クラスのイラスを extends しているか、または 1 つのインタフェー
ンスタンスメソッドをそのまま呼び出せる→つまり、スを implements しているだけであれば」名前を省略で外側クラスのインスタンスを「覚えて」いる→その代
きる→無名クラス
わり new で作り出せるのはインスタンスメソッドや □ 具体的には次のような形
コンストラクタの内側→「内部クラス」と名付けた

public class X {... new Y(); ... ←ここでだけ使用• さらに内部クラスの中からは、インスタンス変数だ
けでなく final 指定の引数や局所変数 (つまり値がclass Y implements I {内部クラスの定義}

変更されない変数) も参照できる←ということは必要なら「メソッドの途中」にも書けるわけ}• これらの機能のため、内部クラスを使うといちいち
アダプタクラスにインスタンス変数を持たせなくてもよい場合が多い→記述が簡単□ このとき、使用しているところに Y の定義本体を直接埋め込んでしまうことで名前を書かなくする
public class X {... new I() {内部クラスの定義} ...
}□ これを使って先の例を書き換えてみた
import java.awt.*;
import java.awt.event.*;22

□ 先の例を無名内部クラスを使うように直すと:□ もう 1 つ課題用のおまけとして、マウスで動かすかわりに時間とともに自動的に動く例題を示す

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;• それには、javax.swing.Timer オブジェクトを「動かす時間間隔 (ミリ秒)」と「その時間間隔ごとに動作する ActionListener オブジェクト」とを指定して作り、メソッド start() を呼べばよい。

public class Sample27 extends JPanel {JLabel l1 = new JLabel("F to C converter.");JTextField f1 = new JTextField();
new javax.swing.Timer(30, ☆).start();
JButton b1 = new JButton("Calc");• この☆のところにまた無名内部クラスを書くことにpublic Sample27() {setLayout(null);
する。中の動作は、「最初の円を sin/cos で位置をadd(l1); l1.setBounds(20, 40, 200, 30);
計算して楕円運動させる」:
add(f1); f1.setBounds(20, 80, 120, 30);add(b1); b1.setBounds(160, 80, 60, 30);
import java.awt.*;b1.addActionListener(new ActionListener() {import java.awt.event.*;
public void actionPerformed(ActionEvent e) {import javax.swing.*;
double f = Double.parseDouble(f1.getText());
l1.setText(f + "F = " + (5.0/9.0*(f-32)) + "C");public class Sample29 extends JPanel {}Figure[] figs = {});new Circle(50, 50, 30, Color.red),}
new Circle(80, 90, 50, Color.blue),
public String getField() { return f1.getText(); }};public void setLabel(String s) { l1.setText(s); }long base = System.currentTimeMillis();public static void main(String[] args) {public Sample29() {JFrame app = new JFrame("Demo");
setOpaque(false);
app.add(new Sample27());
new javax.swing.Timer(30, new ActionListener() {app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
public void actionPerformed(ActionEvent e) {app.setSize(400, 300);
long cur = System.currentTimeMillis();app.setVisible(true);double t = 0.001*(cur - base);}
figs[0].moveTo(100+((int)(90*Math.sin(t))),}120+((int)(60*Math.cos(t))));
repaint();□ ここで、マウス入力関係はどうなっていたかを説明する}}).start();• マ ウ ス 用 の ア ダ プ タ の イ ン タ フェー ス は}MouseListener と MouseMotionListener。な
public void paint(Graphics g) {for(Figure f: figs) { f.draw(g); }ぜ分けてあると思いますか?
}• どちらも、「押し/離し」「ドラグ/ムーブ」など複数
public static void main(String[] args) {のメソッドが定義されている
JFrame app = new JFrame("Demo");app.add(new Sample29());• 興味ある (利用したい) イベントに対応するメソッド
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);だけ書きたいので…
app.setSize(400, 300);
app.setVisible(true);• MouseAdapter と MouseMotionAdapter という「全}部のメソッドが書いてあって何もしない動作になっ }ている」クラスが用意されている。このクラスを継 interface Figure {public void draw(Graphics g);
承して、書きたいメソッドだけオーバライドするの
public void moveTo(int x, int y);が便利
public void setColor(Color c);}• JPanel
な ど は
addMouseListener()、

addMouseMotionListener() を 持って い る ので、これを呼び出してアダプタをセットしている。
• 以上をすべてまとめると先にやったようなものに:addMouseListener(new MouseAdapter() {public void mousePressed(MouseEvent e) {
//中身
}
});
addMouseMotionListener(new MouseMotionAdapter() {public void mouseDragged(MouseEvent e) {
//中身
}
});

class Circle implements Figure {int gx, gy, rad;Color col;public Circle(int x, int y, int r, Color c) {gx = x; gy = y; rad = r; col = c;}public void draw(Graphics g) {g.setColor(col);g.fillOval(gx-rad, gy-rad, rad*2, rad*2);}
public void moveTo(int x, int y) { gx = x; gy = y; }public void setColor(Color c) { col = c; }}23□ 記述が短く簡潔になるという点は便利だが、最初はちょっ• ヒント: 配列 arr 内で整数が大小順に整列されるようにしておくと、和集合や積集合の計算が速くでと分かりづらいきる。

□ 従来の言語で「クロージャ」と呼ばれる機能を持つもの• ヒント: 別の方法として、配列に順に詰めるのではなくハッシュ表などを使ってもよい。API ドキュメンがある• クロージャ = 関数 + 環境

トで調べて Java 標準ライブラリに含まれるハッシュ• Smalltalk のブロックも一種のクロージャ

表のクラスを活用してもよい。

• そのクラス版が Java の内部クラスだと考えられる□ (5) 2 つの Intset どうしが同じ集合であるかどうかを判断するメソッドを実装せよ。また、その性能について

9.4

本節のまとめ前と同様に所要時間のモデルを考え、計測により確認し、□ Java の入れ子クラス→クラスの中に下請けクラスが書ける

さらに改良を試みよ。

• 注記:□ 内部クラス→外側インスタンスの情報や、外側メソッドの局所変数などに中から直接アクセスできる→クロー

前 2 問で行なった改良が含まれた状態でやってもよい。

• ヒント: 集合に含まれる文字列群全体のハッシュ値を計算し、集合が異なればハッシュ値も異なるように

ジャ機能を実現したものと言える

しておくと、異なる場合の判断はすごく速くできる。
• うまく使えば記述がコンパクトになるが、一方でどこで何をアクセスしているか分かりにくくなる恐れ □ (6) 「円」「長方形」などの図形が表示される例題のどもある
れかを改良して、もっと複雑な図形を追加してみよ。そ□ ついでに:のような複雑な図形を作る場合、実装をどのように工夫

オブジェクト指向言語の諸側面のまとめするべきか検討すること。
• 非常に多数の機能がある、ということは分かったと□ (7) GUI 部品を使って何か役に立つ計算をするプログラ
思う
ムを作ってみよ。どのような画面配置にするのがよいか、• それらすべてに「発明された理由」はもちろんあるプログラム内部はどのような構造にするのがよいか、検• しかし「それらがすべてが今いるのか?」は別の問題討すること。
• 言語の多様な機能を使えば使うほど「難しく」もなる □ (8) 円が自動で動く例題では、図形の動きは外側から→「機能の増加」と「簡潔に/分かりやすく記述でき
操作していた。しかしそれぞれの図形が自分固有の「速る」のトレードオフについて常に考える (例: Java度」を持って動く方がオブジェクト指向らしい。そのよvs C++)
うな形にクラス群の機能を拡張してみよ。できれば「速度」などの情報は実際に動く図形にだけ付随しているこ

10とが望ましい。(複数の実装方法を試してみられるとな

第 2 回課題

およい。)□ 以下の課題から 1 つ以上 ((8) を含む場合は 2 つ以上) □ (9) その他、Java または C++を用いて「オブジェクトを選択してプログラムを作成し、報告せよ。考察までき
指向言語らしい」プログラムを作成せよ。
ちんと書くこと。
□ (3) 例題の Intset クラスはプログラム内で生成した整数データをもとに集合を作る方法を提供していない。これを追加せよ。次に、2 つの集合の大きさ M、N と和集合や積集合を計算するメソッドの実行時間との関係を、ある程度大きな M、N で複数通り試してグラフに描け。この実行時間を高速化する方法を考えて実装できるとなおよい。

• 注記: 実験用に乱数が必要な場合、次の方法で「0以上 N 未満の整数の一様乱数」が生成できる。
C++: int((double(rand()) / RAND_MAX) * N)
Java: (int)(Math.random() * N)24