List 2 の使用例 * stepl : 単純な TaIker/Listener if ( listener— ) { void changed( ) { listener— = listener; VOid setListener ( Listener* listener—(0) ( } Talker() ・ public: Listener* listener— private: class TaIker { v 辷セ u 引 void update(TaIker* talker) = virtual -Listener( ) { } public: c ー ass Listener { class Talker; using namespace std ー # inc lude <iostream> listene て ) ー→ update ( this public: 土 n セ va ー ue— private : * お試しコード listener 80 c lass Counter : pub lic Tal ker { counter( ) : value 」 0) { } class DigitPrinter : public Listener { changed( 十十 value— void increment( ) { int getValue( ) const { return value— cout < く static—cast<Counter*>(talker)—>getValue( ) くく endl; v 土てセ u 引 void update(TaIker* talker) ( List 4 class Listener ( public: v 土て加引 -Listener( ) { ) v 土てし u 引 void update(TaIker* talker) = の class TaIker ( private : std : : vector<Listener*> ー isteners— public: TaIker() { } if ( std: :find( listeners-.begin( ) , listeners— void addListener(Listener* listener) { void changed( ) { listeners—.clear( void removeListeners ( ) { listeners— . erase(it); if ( ! = listeners-. end( ) ) { ー isteners— std: :find( listeners— . begin( ) , std: :vector く Listener*>.• :iterator 土し = void removeListener(Listener* listener) ( listeners-.push—back( listener); listeners-. end( ) ) { (*辻) → update(this); while ( ! = listeners— . end( ) ) { std: :vector く Listener*>: :iterator it = listeners— listener) listener); . begin ( 十十土 t ー * お試しコード C ー ass Counter private : int value— public: : public TaIker { int main( ) ( Counter c ー DigitPrinter d ー C. setListener(&d); fo て ( 土北土 = 土く c. increment( return 0 ー 十十 i vecto 「を使って 1 つの Ta ⅸ e 「に複数の Listene 「を対応させる * Step2 #include #include : ひとつの T 引 ke てに複数の L 土 s セ ener # inc ー ude く iOSt て eam> く vector> #include く algorithm> く string> counter() : vaIue-(0) { } int getVaIue( ) const { return value—; } void increment( ) { 十十 value— changed( using namespace std; class DigitPrinter : public Listener ( virtual void update(TaIker* talker) { cout くく static—cast<Counter*>(talker)—>getValue( ) くく endl; cout くく string(static—cast く Counter*>(talker)->getValue( ) , ' * りくく endl; virtual void update(TaIker* talker) { class Starprinter : public Listener { return 0 ー c. increment( fo て ( int i = の i く十十土 ) { c. addListener(&s); c. addListener(&d); starprinter s ー Digitprinter Counter C ー int main( ) ( class Talker; C MAGAZINE 2001 4
オプジェクト工房 List 7 template を用いた Talker/Listene 「 class Counter : public Talker<int> ( private: 土 n し value— public: : more generic! (using template' ) * Step4 #inc ー ude く iostream> #incl ude く vector> #incl ude く set> #inc ー ude く a ー gorithm> #inc lude く string> * ListenerBase C ー ass ListenerBase { private : static std : : set く ListenerBase*> va ー ids— public: Listene て Base( ) { valids— . insert(this); } virtual -ListenerBase ( ) { va lids— . erase ( this ); } static bo 引 isVaIid(ListenerBase* listener) { return valids— . find( listener) ! = valids— . end( counter( ) : value 」 0) 0 void increment( ) ( 十十 va lue— changed(value—); using namespace std; class DigitPrinter : public Listener く int> { virtual VOid update(const int& value) { cout くく value くく endl ー cl ass StarPrinter : pub lic Listener<int> { v 土てセ u 引 void 土れセ & value) ( cout くく string(value, ' * ' ) くく endl; std : : set<ListenerBase*> : : va ー ids— template く class Event> class Talker; / / forward decl . * Listener<Event> temp ー ate<C ー ass Event> class Listener : public ListenerBase public: virtua ー VOid update ( const Event& ) * Ta lker<Event> template«class Event> class TaIker { private: std : : vector く Li stener<Event>*> ー isteners— public: Talker() 0 void addListener ( Li stener<Event>* listener ) { if ( std: :find( listeners-. begin( ) , ー isteners— listeners— . end( ) ) { listeners— . push—back(listener); int main( ) { Counter c ー DigitPrinter d; StarPrinter* S = new StarPrinter ー c. addListener(&d); 0. addListener(s); c. increment ( / / ここで不慮の死を遂げても delete 町 c. increment(); / / 迷惑をかけない . return 0 ー Ta ⅸ e 「から導出しない Counte 「 * Step4' : T 引 ke てから導出しない coun セ e て 〃 Talker, Listener, DigitPrinter, starp て inter はほかのコードと同じ ass Counter { private : int value— Tal ker く int> tal ker public: listener ) VOid removeListener ( Listener<Event>* ー istener ) std : : vector<Listener<Event>*> : : 土し era と 0 て it ー istener ); std : : find ( listeners—. begin ( ) , listeners- if ( it ! = listeners-. end( ) ) { listeners— . erase(it); counter() : vaIue-(0) { } TaIker<int>& talker( ) { return talker—; ) void increment( ) { 十十 va ー ue— talker( ) . changed(value—); void removeListeners( ) { listeners-.clear( VOid changed ( const Event& event ) { . beg 土 n ( Std : : vector<Listener<Event>*> : : 土セ e て atO て listeners— while ( 土セ ! = listeners— . end( ) ) { if ( ListenerBase: :isValid(*it) ) ( (*it)->update(event); 十十 it; } else ( it = listeners-.erase(it); 加セ main( ) { Counter c ー DigitPrinter StarPrinter* s = new StarPrinter ー c. talker( ) . addListener(&d); c. talker( ) . addListener(s); c. increment( delete 町 0. increment( return 0 ー コ し 試 お 8 イ 2001 4 C MAGAZINE
オフジェクト工房 temp ー ate<C ー ass Event> class TaIker { Step4 がほしいのは というわけで , 複数の Listener にも存在 しない Listener にも対応できました。です が , ぼくにはこれでもまだ不満が残るんで す。それというのも , Listener::update() の 引数が気に入りませんム 。フ , Listener::upd ate ( ) の引数は TaIker * となっていて , イ べントの発行元である Talker へのポインタ が引き渡されます。ですから , たとえば dig itprinter::update (Talker * talker) では引数 として渡された TaIker * から Counter * に キャストし , さらに Counter のメソッド get VaIue ( ) でカウント値を取得し , プリント ・・カッコ悪いとは思いませ しています。 んか ? だってこんなコードを書いたとた ん , DigitPrinter は Counter からのイベント しか受理できないことになってしまうじゃ ないですか。ⅲ t 値をプリントするのが Digi tprinter の仕事であり , こいつを update ( ) するのが誰であろうが , プリントすべき int 値さえ手に入ればいいのであって , イ べントの発行元なんか知る必要はないはず です。 DigitPrinter::update() の引数として必 要なのは , プリントすべき int 値です。そ うでしよ ? だから DigitPrinter::update ( ) の望ましいインタフェイスは , void DigitPrinter : :update ( Java で書いた Counte 「 /DigitPrinter const int&) なんです。しかしそうすると基底クラスの 仮想メソッド Listener::update() も同じシグ ニチャ , すなわち , class Listener { virtua ー VOid update (const int&) = の て自由に選びたいのに , int 決め打ちでは 引数は Listener の導出クラスの求めに応じ でなくてはならなくなります。 update() の public: VOid VOid VOid addListener (Listener く Event * > * て emoveListener (Listener く Event * > * ) ー changed (const Event&) ー 致悪 ' でしかありません。 らんものでしようか。 ・ template を使う ・・なんとかな 解決策は存在します。 template を使うん です。 Listener を , update() に引き渡した い型を template 引数とした class template と します。 temp late く c ー ass Event> class Listener { virtua ー void update (const Event&) = 0 ー こうすれば DigitPrinter は Listener く int> か ら導出できます。 Listener の template 化に対応して , Talker にも同様の変更を施します。次のような感 じです。 最終的にできあがった template 版 Talker /Listener を List 7 に示します。 Talker から L istener への引数が Talker でなくなったこと で , イベント発行元は必ずしも Talker から 導出する必要がなくなり , より自由になり ました (List 8 ) 。 いかがでしようか。イベント駆動のきわ めて単純なからくりからここまで、凝る ' とができます。ばくのディスクの中にはさ らにもう一歩進めて・ update ' というメソッ ド名までも自由に取り替えることのできる TaIker/Listener が眠っています。いつか機 会があればお披露目しましよう。こうやっ ていじくり回して遊んでいるもんだからオ プジェクト工房は・試作品 ' ばっかりで , い つまでたっても・完成品 ' が生み出されてこ 終わりに ないんだよなあ・・ * stepl : 単純な TaIker/Listener - import java. util . ObservabIe; import java. util.Observer; ー Java version class Counter extends ObservabIe { private int value— public counter( ) ( value— public 土 n セ getvalue( ) { return value—; ) public void increment( ) ( 十十 value—; setChanged( notifyObservers( class DigitPrinter implements Observer ( public void update(ObservabIe from, Object arg) ( system. out. println( ( (counter)from) . getValue( ) public class stepl { public static void セ r 土記 (Counter c) { DigitPrinter d = new DigitPrinter ( c. addObserver(d); c. increment( public static void main(string[ ] args) { Counter c = new Counter( tria Ⅱ c c. increment( どびてククのオブジェクト工房 83
対し removeListener (this) すればいいでし よう。そうすれば changed ( ) のたびに生死 を確認する必要がなくなります。こちらは ぜひみなさん自身の手で挑戦してください。 ・ Java の場合 こでちょっと道草をします。これと同 じことを Java でやるとどうなるでしようか。 Java クラスライプラリが提供する java. util. Observable , java. util. Observer が TaIker , Listener と機能的にはまったく同じです。 Counter は ()bservable を extends , DigitPri nter は Observer を implements することで実 現できます。 Java ではオプジェクトを new 安全な方法にした Talker/Listene 「 く string> #include <algorithm> く set> く vector> く iostream> : 安全な Talker/Listener #include #inc lude # 土 n ude #incl ude * Step3 virtual ¯Listener( ) { valids— . erase(this) Listener( ) { valids— . insert(this); } public: static std; :set<Listener*> valids— private: friend class Talker; class Listener { listeners—.push—back( listener); listeners— . end( ) ) { if ( std: :find( listeners-.begin( ) , VOid addListener(Listener* listener) TaIker() { } public: std : : vector<Listener*> ー isteners— private: class Talker { std : : set く Listener*> Listener : : va ー ids— virtual void update(TaIker* セ引 ke て ) = の ー isteners— listene て s— std: :find( listeners—.begin( ) , std: :vector<Listener*>: :iterator it VOid removeListener(Listener* listener) ( することはあっても明示的に delete するこ とはありません。オプジェクトが使われて いないと Java 仮想マシンが判断した時点 で , 自動的にあと始末が行われます。 List 6 が Java で書いた Counter/DigitPrinte r です。メインルーチンは , Counter c = new Counter ( new DigitPrinter ( DigitPrinter d C. increment ( ) ー c. addObserver (d); d = nul 嵭 } 引 se { C. increment ( ) ー のように途中で DigitPrinter d がスコープ から外れています。これを実行すると・・ あら不思議 , 最後の increment で飛ぶこと なくカウント値がプリントされます。 時点で d はまだ死んでいないようです。 C. a この すかもしれないからです。 でも生き残り , じわじわとメモリを食い潰 い終わったつもりのオプジェクトがいつま 面 , ちょっと怖い気がします。自分では使 Java 仮想マシンって賢いなと感心する反 き残っているんですね。 ているので , あと始末の対象にならずに生 ddObserver(d) の時点で d は c から参照され it = listeners— . erase(it); * お試しコード c 1 ass Counter private : int value— public: : public TaIker ( listener) listener); if ( 辻 ! = listeners-. end( ) ) { listeners— . erase(it); void removeListeners ( ) { listeners-. clear( void changed ( ) { std: :vector<Listener*>: : 土 te て a セ 0 て it = while ( 土セ ! = listeners-.end( ) ) { if ( Listener: :valids— . find( *it) ! = Listener: :valids— . end( ) ( *it)->update(this); listeners—. begin ( counter() : value-(o) { } int getValue( ) const { return value— void increment( ) { 十十 v 引 ue changed( using namespace std ー class DigitPrinter : public Listener { cout くく static—cast<Counter*>(talker)—>getValue( ) くく endl; virtual void update(TaIker* talker) { return 0 ー c ・ increment(); / / 迷惑をかけない . delete 町 / / ここで不慮の死を遂げても 0. increment ( c. addListener(s); c. addListener(&d); StarPrinter* s = new starprinter ー Digitprinter d ー Counter c ー 土北 main( ) { cout くく string(static—cast<Counter*>(talker)->getValue( ) , ' * ' ) くく endl; v 土てに u 引 VOid update(TaIker* セ引 ke て ) { class S セ a て P て土 n し e て : public Listener { 82 十十 it; C MAGAZINE 2001 4
た方は少なくないと思います。標準入出力 によるオーソドックスなプログラム , いわ ゆるコンソールアプリケーションでは , ま ずメインルーチンからスタートし , そこか らサプルーチンを何重にも呼び出しながら 処理が進行しています。たくさんの関数が メインルーチンを頂点とする木構造をなし ていて , どの関数であってもメインルーチ ンを起点に呼び出しを追いかけていけば必 ずやたどり着くことでしよう。 ところが GUI アプリケーションは違いま す。 ・ボタンが押されたら XX する ・キー入力があったら 00 する のように , プログラムは " 何かが起こる ( イ べント ) " と " そのとき何をする ( アクション ) " の集合体であり , 木構造にはなっていませ ん。たくさんのアクションの断片が浮島の ごとくコード上に散らばっているだけで , 「こんなのが何で動くんだ ? 」とさえ思えて きます。 イベントとアクションを結び付けるから くりは , V1sual Basic や Borland Delphi , あ るいは Borland C + + Builder などの RAD ツー ルでは , 密かにどこかに埋め込まれ , さほ どおおっぴらにはコード上に姿を現しませ ん。 VisuaI C + + が提供するフレームワーク である MFC では , メッセージマップと呼 ばれるマクロ群がイベントとアクションと MFC に見るイベント駆動の仕組み public: MyDoc() : value-(0) { protected : int va ー ue— private: class MYDOC : public CDocument ( - Document-side * MFC's Document/View sample ー view-side UpdateAllViews(0); / / View を更新 ! 十十 value— void increment( ) { 〃 MyDoc 内のデータに変化を及ぼすメソッド 土 n セ getvalue( ) const { return value—; の対応表として機能しています。 ・イベント駆動の使われ方 アクションがイベントによって起動され る「イベント駆動」のからくりは , マウスや キー入力のようなハードウェアに起因する イベントだけでなく , ソフトウェアがイベ ントを起こしてアクションに火をつけると いう使われ方がされることもあります。例 としては , MFC の Document/View アーキ テクチャにこれを見ることができます。 アプリケーションが取り扱うデータをつ かさどる Document と , その表現 ( 見てくれ ) であるⅥ ew を用意して , その両者を関連付 けておきます。 Document は内包するデー タに変化が起こったときにイベントを発し , それによって関連付けられた View 側のア クションが起動されます。 MFC では CDocument のメソッド Update AllViews ( ) をコールすることがイベントの 発行であり , それをトリガとして CView の メソッド OnUpdate ( ) が呼び出されます。 OnUpdate ( ) 内の処理によって CDocument 内にあるデータの変化を見てくれに反映さ せるわけです (List 1 ) 。このからくりのお かげでデータの本体とその見てくれを分離 し , お互いの変更や拡張が他方に及ぼす影 響を小さく抑えることができます。 ・・要はコッチに何かが起こったことを オフジェクト工房 Talker クラスと Listener クラス アッチに伝えるということです。なんてこ とはないからくりなんだけど , いろんな応 用が考えられるし , さまざまな " ひねり " を 加えることができて , これがけっこうおも しろいんですよ。イベント駆動のきわめて 単純なからくりをこさえ , それに少しずつ " ひねり " を加えながら設計と実装の多様性 を楽しんでみようと思います。 class Talker; class Listener { public: virtual -Listener ( ) 0 virtual void talker) = の class TaIker { private: Listener* listener— public: Talker( ) void setListener(Listener* listener) { ー istener— = ー istener ー void changed( ) { i f ( listener— ) { listener—->update(this); Iistener-(0) { } stepl 単純な Talker/Listener では , まず元ネタとなる " 単純なからく class MyView : public CView { public: MyDOC* GetDocument ( / / たとえばマウスクリック時に MyDoc : : 土 nc て ement ( ) すると、 VOid OnLButtonDown(UINT, CPoint) { GetDocument ( ) —>increment ( / / MyDoc: :increment( ) 内での UpdateAIIViews( ) により . virtual VOid OnUpdate(CView* , LPARAM, CObject* ) { lnvalidate( / / 再描画 - ー OnD て aw ( ) が呼ばれる / / Doc の値 ( getv 引 ue ( ) ) を表示する virtual void OnDraw(CDC* pDC) { CString va 嵭 val . Format( d ” ,GetDocument( ) → getvalue( ) pDC->TextOut(0,0, val 嶹 ど 0 てククのオブジェクト工房 / 9
五ロ - 三反 ンパイル結果の定義には違いがありません。 C + + では公開 , 非公開の指定はあまり美 ラスの定義内のほかの定義を文法上壊さな ところが , どのようなコードを記述しても しくない方法で行うことになっています ( 少 い位置ならどこでも何度でも現れることが CPerson のフィールド [field] である pos_x, なくとも筆者は美しくないと感じていま できます。これが筆者があまり美しくない pos-y, life を変更することはもちろん , 参 す ) 。すなわち , C 言語の goto で使われるラ という理由です。 照することさえもできません。これは , C + + ベル構文と予約語 public, private でアクセ たとえば , 巨大なクラスの一部を見せら ではクラスのフィールドは , デフォルトで ス制限を決定するのです。 れたとき , その部分が公開なのか非公開な は非公開 [private] になっているからです。 List 3 , 4 はそれぞれ List 2 の CPerson のア のかを判断するためには , public あるいは p C + + の基本事項の 1 つはプログラム上の概 クセス制限を公開に変更したものと , SPer ⅱ vate のラベルが現れるまでさかのばりが必 念 [object] をクラスで包んで [ wr 叩 ] し , 利 son のアクセス制限を隠蔽に変更したもの 要です。最近の賢い IDE 環境でもクラス内 用者には不必要な内部構造を見せないよう です。 public, private は構造体あるいはク の現在のアクセス条件に応じて , 表示変更 にすることです。このため , クラスのフィ アクセス制御 ( I) アクセス制御 ( 2 ) ールドは公開 [ pub ⅱ c ] 部分に宣言しない場 合は非公開になります。非公開という意味 / * fi d すべてが公開 * / / * fi 引 d すべてを隠蔽 * / を正確に理解することは , 講座を読み始め struct SPerson { struct SPerson ( private : たばかりの現時点での知識だけでは無理か int pos—y ー int pos—x ー 土 n し life ー int 008 ー y ー もしれません。しかし , List2 によって C + + } SPerson ー int は f } SPerson; でのクラスと構造体の違いの 1 つは「デフォ class CPerson { public: class CPe て 80 れ { ルトでのアクセス制限」であることがわか 土 n し 008 ー x ー int pos—x ー 土 n セ 008 ー y ー int pos—y ー りました。では , このアクセス制限はどの 土 n しは f int life ー } CPerson ー } CPerson ー ようにしてプログラマが制御するのでしょ うか ? List List / * ここから先はすべて隠蔽 * / / * ここから先はすべて公開 * / Fig. 2 変数の宣言とメモリ割り当て (a) C + + の場合 hogel . cc int f00 ー (b) C の場合 hogel ℃ int f00 ー hoge2. cc int f00 ー hoge2. c 土 n し f00 ー コンパイル コンパイル コンバイル コンパイル . comm セクショ ンにエントリ . comm セクショ ンにエントリ メモリに割 てられた f00 メモリに割 てられた f00 リンク リンク 多重定義工ラーが発生 f00 の実体がどこにもな いので , リンカが実メモ リを割り当てて新規作成 実行形式ファイル 43 特集 プログラミング入門 c + + 言語入門講座
五ロ ろ描けると思います。「おじゃる丸」の顔く GUI やいろいろな話題を取り上げたいと思 とにかく GUI プログラムは , いろいろ約 束事が多くてたいへんなので , 文法を説明 っています。 らいならなんとかなるのではないでしよう みなさんは , できればただ読むだけでは する間しばらくお休みします。 よければ , その間 , List 2 の「〃描画」と メソッドの引数などの使用は , JDK ドキ なく , 処理系をいつも使える状態にしてお いて , 何かプログラムを作ってみてくださ あるところをいじって遊んでいてください。 ュメントの Graphics クラスのところで調べ い。習うより慣れろと言いますが , Java の List 2 の fiIIRect というメソッドは長方形を塗 てください。 りつぶすものであり , 皿 OvaI は楕円を塗り 場合 , 習うのと慣れるのと並行してやると 理解しやすいと思います。そしておもしろ つぶすものです。 いプログラムができたら , 編集部まで送っ この場所で使えるメソッドには , ほかに も , 塗りつぶさずに長方形を描く drawRect, 今回は Java のプログラミングのだいたい てください。 の流れを見てきました。 7 回目 ( 10 月号 ) ぐ 楕円を描く drawOval, 直線を引く drawLine 次回はクラスとオプジェクトについての などがあります。このくらいあればいろい らいまでは文法部分を解説し , そのあとは お話です。 日本 , バングラデシュ , パラオ , リビアの 国旗を表示するプロクラム ( 曰 ag2. java ) 今後の予定 List if(). getsource( ) = = BangladeshButton) { centerColor = C0ー0て . red; backColor = C0ー0て . green; if(). getSource( ) = = PaIauButton) { centerCoIor = C 引 0 て . ye Ⅱ OW ー backCOIor = C0ー0て . cyan; if(). getsource( ) = = LibyaButton) { centerColor = C0ー0て . green; backColor = C0ー0て . green; / ん P の再描画 panel . repaint( 4 ミ ) 【′ 8 0 1 宀内 0 4 ノ′ 0 ワ・ 8 0 1- っつ 4 Ln 「′ 8 0 ・↓っ 1 っ 4 5- -0 っ′ 8 0 1 宀っっ 4 5- っー 8 0 1 宀っっ 1 4 よイよ 4 ↓ * FIag2. java 2 : 3 : 4 : 5 : import java. awt. *ー 6 : import javax. swing. * ー 7 : import java. awt. event. * ー 8 : 9 : class Flag2{ / / 背景と中央の色 10 : / / デフォルトの色はこ ap 明 11 : static CO て centerColor = COIO て . て ed ー 12 : static Co ー 0 て backColor = COlor. white; 13 : 14 : public static void main(String[ ] args) { 15 : 16 : / / 旗を描画する JP 17 : final JPaneI panel = new Jpanel( ) ( 18 : / / 描画 19 : public void paintComponent(Graphics g) ( 20 : guper. paintComponent(g); 21 : 22 : g. setCOIor(backColor); g. fi Ⅱ Rect ( 0 , 0 , 300 , 200 24 : g. setCO ー 0 て ( centerCo て g. f 土日 Ov 引 ( 100 , 50 , 100 , 100 26 : 28 : 29 : 30 : 31 : 32 : 34 : 36 : 38 : 39 : 40 : 41 : 44 : 48 : 50 : 51 : / / リスナを登録 JapanButton. addActionListener(l BangladeshButton. addActionListener(l PalauButton. addActionListener(l LibyaButton. addActionListener(I / / パネルに横に並べる JPanel buttonPanel = new JPanel( buttonPanel.setLayout(new GridLayout(), 0 ) buttonPaneI.add(JapanButton); buttonPanel.add(BangIadeshButton); buttonPanel.add(PaIauButton); buttonpanel . add(LibyaButton); / / 全体を乗せるて明 e JPrame frame = new JFrame( "FIag2" / / クローズしたらプログラムを終了するようにする frame. addWindowListener(new WindowAdapter( ) ( public VOid windowClosing(WindowEvent e) { s し 5. 土 t ( 0 panel.setPreferredSize(new Dimension( 300 , 200 ) 〃旗を変更するボタン final JButton JapanButton = new JButton("Japan"); / / デフォルトで Jap を選択 JapanButton. setse ー ected ( true final JButton BangladeshButton = new JButton( "Bangladesh"); final JButton PalauButton = new JButton("Palau"); final JButton LibyaButton = new JButton( "Libyan 潺 //Button のグループ化 ButtonGroup group = new ButtonGroup( group. add(JapanButton); group. add(BangladeshButton); group. add(PaIauButton); group. add(LibyaButton); / / ボタンのリスナ ActionListener ー = new ActionListener( ) ( public void actionPerformed(ActionEvent e) { 〃ボタンごとに色を設定 if(). getsource( ) = = JapanButton) { centerCoIor = Co て . て ed ー ckC 引 0 て = C0幡て . white; Container contentpane = frame. getContentPane ( contentpane. add(panel) ー contentpane. add(buttonPaneI, BorderLayout. SOUTH); frame. pack( frame. setVisible(true); 特集プログラミング入門 Java 言語入門講座 51
00 フログラミング入門 きます。その中でも , とくに重要ないくつ ましたが , 最初だし , 雰囲気を知ってもら の量がいきなり 3 倍くらいになってしまい かのクラス ( java. lang パッケージに入ってい うためにちょっとだけお見せしましよう。 るもの ) は , 手続きをしなくてもいつでも ただし解説はしません。 グラフィックを使うプログラムはやつば List2 は , 日の丸を描画するプログラムで 使えるようになっています。 System もそれ りめんどうであり , それに動きを持たせる であり , これには , プログラムを実行して す。コンパイルして , とさらにめんどうになるのです ( それでも , いるコンピュータシステムのあれやこれや Java でやるグラフィックプログラミングは java Flag がまとまって入っています。 簡単なほうです ) 。 と実行させると , Fig. 4 のようなウインド それに続く out は , システムのコンソール ウが現れます。 GUI プログラムと CUI プログラムでは , 複 出力を表し , それが p ⅱ ntln ( ) というメソッ つまらないですか ? たしかに , 日の丸 1 雑さだけではなく , プログラムの動作原理 ドを持っています。これに文字列を渡せば 種類だけではつまらないかもしれませんね も異なっています。 CUI プログラムは , mai 表示できるわけです。 ( そういう問題ではないかもしれませんが ) 。 n から始まって動作の流れが続いていくと そこで , 日本・バングラテシュ・パラオ・ いう傾向が強く , 一方 GUI プログラムは , [ 注 3 ] 実はこの説明は不正確です。図中の機械の ようなものをクラスといったのは問題があ リビアの 4 か国の旗を表示するプログラム ボタンとかウインドウとかが , ユーザの操 り , 本当は「オプジェクト」というべき。し かし今のところは上記のようにイメージし も作りました。 List 3 をコンパイルして , 作をじーっと待っていて , 「ロロという操 てください。クラスとオプジェクトがどう 作をされたら△△という動作をしよう」と Java Flag2 違うかは次回説明します。 と実行させると Fig. 5 のようなウインドウが 考えています。つまり何か事が起こること 出て , 下のボタンを押すとそれぞれの国旗 によってプログラムが働くのです。こうい に変わります。 うやり方をイベントドリブン ( イベント駆 グラフィックはしばらくやらないと言い この程度のことをやるだけでも , ソース 動 ) といいます。 日の丸を描画するプログラム (Flag. java) Fig. 4 Flag. java (List 2 ) の実行例 2 : * plag. java 3 : 4 : 5 : import java. awt. *ー 6 : import javax. swing. *ー 7 : 土 mpo てセ java. awt.event. * ー 8 : 9 : class をね g { 10 : public 8 ね土 c void main(string[ ] args) { 11 : 〃旗を描画する JP 聞引 12 : final JPaneI panel = new 明 e Ⅱ ) { 13 : / / 描画 14 : public void paintComponent(Graphics g) { super. paintComponent(g); 16 : 17 : / / この下を変えるといろいろな図形が描ける 18 : 9. SetC引0て(C引0て . white); g. f 土日 Rect ( 0 , 300 , 200 20 : g. getCO て ( C 引 or. て ed 21 : g. f 土Ⅱ Ova Ⅱ 100 , 50 , 100 , 100 22 : 24 : 26 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 38 : グラフィックを使う プログラム List Fig. 5 Flag2.java (List 3 ) の実行例 F g2 panel . setpreferredSize(new Dimens 土 on ( 300 , 200 ) / / 全体を乗せるて明 e JFrame frame = new JFrame("Flag" frame. addWindowListener(new WindowAdapter( ) { public void windowClosing(WindowEvent e) { system. exit( 0 Ba 明ね面 frame. getcontentpane( ) . add(panel frame. pack( f て明 e. setv 土 8 土 b 厄 ( t て ue PüI Japan 50 C MAGAZINE 2001 4
Java programmingTips インスタンスごとにスレッドを使うので , ffread クラスを継承しています。そしてク ライアントに対してメッセージを送信する send ( ) メソッドです ( List 1- ⑩ ) 。これも synchronized にしています。 send ( ) メソッ ドの内部では , 改行文字を「 \ n 」に統一し たうえで BufferedWriter を使ってメッセー ジを送信し (List 1- ⑩ ) , 送信バッフアをフラ ッシュします ( List1- ⑩ ) 。一般的な端末ソ フトでは改行文字が「 \ n 」になっているこ とにご注意ください。 List 1- ⑩からはメッセージ受信スレッド から実行される run ( ) メソッドです。まず接 続したソケットを使って入出力ストリーム を作成します ( List 1- ⑩ ) 。このとき第 2 引数 でエンコーディングを指定しています。ス トリームを作ったら , 接続メッセージ表示 やハンドル名の入力を経て , メッセージ受 信待ちループに入ります ( List1- ⑩ ) 。ここで は BufferedReader を用いてメッセージを受 信し , ハンドル名を付加したあとに , 前述 の ChatServer クラスの broadcast() メソッド を呼び出して全クライアントにメッセージ を配信しています。 最後はクライアントとの接続が途絶えた ときに , ストリームやソケットを閉じる処 理です (List 1- ⑩ ) 。 トチャットクライアント telnet などの端末ソフトでは入力メッセ ージと受信メッセージが混ざって表示され てしまうため , チャットに使うにはやや不 便です。そこで , チャットクライアントを 作ってみました ( Fig. 3 ) 。サーバと対になる クライアントの作り方の参考にしてくださ 付録 CD-ROM に収録されている ChatClie nt.java を , javac ChatC lient. java でコンパイルしたあと , java ChatClient ホスト名ポート番号 工ンコーティンク で実行します。たとえば , チャットサーバ を前述のように動かしておいた場合 , サー バが動いているマシン上で , Fig. 3 チャットクライアント 、上 Ohat 0 lient VVelcome Java Chat Setver. Make sure tO use SJIS encoding Please enteryour handle name. OK. Thankyou foraccess, ひぐ 10 : 33 : 17 ひぐ logged れ 10 : 33 : 25 ペん logged in. 10 : 33 : 38 ひぐ】ネットワークゲーム 10 : 33 : 57 いん ] 蒸し風呂 10 : 34 : 04 ひぐ ] 論理学 10 : 34 : 10 パん ] 苦悶の表情 10 : 34 : 17 ひぐ ] 打たれ強さ 10 : 34 : 22 [ ペん ] 沙羅 10 : 34 : 25 ひぐ】湯たんま 10 : 34 : 28 ん】ポニーテール 10 : 34 : 32 ひルートディレクトリ 10 : 34 : 36 いん ] 輪転秤 10 : 34 、 49 ひぐ ] 槭の体 1 0 : 34 : 54 いん ] 六八車 10 : 35 : 02 ひぐ ] まじめこ仕事しよう・ 10 : 35.07 いん ] うん・ 先週は PSOI こハマってしまったことだしな・ 0 0 0 List チャットクライアント import java. 土0. * ー mpo てし java. net. *ー import java. awt. * ー import java. awt. event. *ー import javax. swing. * ー public class ChatClient extends JFrame implements Runnable ( / / ソケット Socket socket ー / / ログ表示領域 JTextArea ー og ー / / 入力領域 JTextField input; ノ / 工ンコーティング String enc; BufferedReader ての / / 入力用ストリーム BufferedWriter wti / / 出力用ストリーム / / ホスト string host; / / ポート int port ー / / アプリケーションのスタートアップ pub lic static void main ( string args [ ] ) (new ChatClient(args) ) . main( / / 使い方を表示して終了 static void usage( ) { System. ou し print ー n ( usage: java ChatClient HostName #port EUC/jIS/SJIS" System. eX土し(1リ 〃コンストラクタ ChatCIient(String args[ ] ) { / / GUI 構築 super("Java Chat Client" getContentPane( ) . setLayout(new BorderLayout( ) 房 getContentpane( ) . add(log=new JTextArea( ), BorderLayout.CENTER 、 log. setEditabIe(false); getContentPane( ) . add(input=new JTextField( ) , BorderLayout.SOUTH); setSize(600,400); setVi sib 厄 ( true ); / / イベント定義 addWindowListener(new WindowAdapter( ) { public VOid windowCIosing(WindowEvent e) { system. eX辻(0嶹 / / 送信処理 input.addActionIÅstener(new ÅctionListener( ) 。て public VOid actionPerformed(ActionEvent ev) string s=input. getText( input.setText("")$ try { 0 1 27 Java Programming Tips
うにすると theAns の値が thel に反映され る。 こでは FORTRAN の SGN( 引数の トーと似ているが , 違いは最後に置かれ た関数 , もしくは値が「 ( ト D 」自身の値に なってしまうという関数パラダイムの香り のする構文である。たとえば , List 2 のよ typeof 構造体の初期化でメンバ名 printf( "thel = 宅 d 基 n ” ,thel thel = SearchDim ( theDat , GetData ( ) printf( "thel = 宅 d 基 n ” ,thel thel = SearchDim(theDat ,GetData( ) = 199 theDat[7] theDat[3J = 200 int the 島 static int theDat[10]; static VOid TestMain ( void ) static 土 n セ GetData ( void theAns; break; 基 if(theData = = DIM[theAnsl) \ for(theAns = sizeof(DIM) /sizeof(DIM[ 0 ] ) , 土 n に theAns ー typeof(DATA) theData = (DATA); 基 #define SearchDim(DIM,DATA) ( { 基 正負判定 ) 的なコードを , ・ theAns > = 0 ー theAns-- ) { typedef struct { int x,y,z; } C00 て p static VOid TestMain(void) CoorType theCT1 = { 1 , 2 , 3 CoorType theCT2 = { z: 3 , y: 冖 ( 略 ) . 2 , x: 1 _FUNCTION__ と __PRETTY_FUNCTION_ くく—FUNCTION—くく” ] 基 n ” cout くく "—FUNCTION—— = static void Func(void) { public: class TestClass { cout くく n—PRETTY—FUNCTION static VOid TestMain(void) [ ”くく——PRETTY_FUNCTION— 真紀俊男の ローテク講座 の内部で展開している。ただ実際にこれが 使われるのは # d ⅶ ne での話みたいだが typeof typeof(X) とすると , X の型を取り出す ことになる。たとえば , List 3 の SearchDi m 内の「 peof ( DATA ) 」は先の例だと , peof(GetData() ) に置き換わり , GetData 関数は int 型なので , 結果的に int theData = GetData ( に置き換わってしまう。 ちなみに peof と似たもので「 typedef A=B; 」という記述方法もあるらしい。っ ちは B の型を取り出し , それを A に入れて 0 は , (DATA) theData つまり , List 3 の , しまうという仕様らしい。 = (DATA) , (DATA) , = (DATA) , にも置き換え可能である。 —type—data theData typedef —type—data = 構造体の初期化でメンバ名 どういうメリットがあるのかよくわから eCTI と theCT2 は同じ値で初期化される。 記述が可能である。たとえば , List4 の th ないが , メンバ名を付けて初期値を与える in ⅱ ne 関数と「″」のコメント 主に開発時のデバッグ用だと思われる FUNCTION と一 PRETTY FUNCTION さら珍しくもないが。 は勝手に採用されていることがあっていま める。ただし後者は最近の C コンパイラで C + + と同様 , 「 / / 」で始まるコメントも認 C + + と同様 , in ⅱ ne 関数が使える。また が _FILE _LINE__ と同様 , どこで 問題が起きたかを告知したいときに重宝す FUNCTION_ —PRETTY—FUNCTION— = [ ″くく—FUNCTION——くく” ] 基 n ”・ [ ”くく—PRETTY—FUNCTION—くく” ] 基 n ″・ TestClass: :Func( くく cout くく 1 40 C MAGAZINE 2001 4 る。ともに関数名を意味するが , PRET TY FUNCTION ーは型情報も含んでい て , より詳細である ( List5 ) 。