オブジェクト指向アプローチ論 第8回
このページは,2007年前期まで実施されていた科目の補助教材ページです。既に履修し終わった方々
の参考のために公開を継続しています。したがって,内容が古いままのものが残っておりますので注意
して下さい。


※2007年度より実施されている2年生選択科目「オブジェクト指向プログラミングa」のページはここ
 ではありません。「オブジェクト指向プログラミングa」のページはこちらです。


●モジュールの作り方「Javaを学ぶ 第9回 (2003年1月 )」

【はじめに】

正常に動作するには,変数(フィールド)を外部から守らなくてはならない


【モジュール】




【逆ポーランド記法計算機】


●逆ポーランド記法




●スタック (stack)








【スタッククラスの作成】




MyStack.java



【クラス外からのアクセス禁止】


AttackMyStack.java







【公開と非公開の意味】



※フィールドは必ず private に指定する
※フィールドを参照したり操作する場合は,次に説明する専用のメソッド(アクセッサ, accessor)を用意すること

【アクセッサ】

 前述したように,フィールドは原則的にprivateにして,クラスの外部からアクセスできないようにすべきである。
そのため,クラスの外部からフィールドの値を変更したり,フィールドの値を参照したい場合には,そのための専用の
メソッドを用意する。このようなフィールドにアクセスするための専用のメソッドをアクセッサ(accessor)と呼ぶ。
特に,フィールドに値を設定するアクセッサをセッタ(setter),フィールドの値を返すメソッドをゲッタ(getter)
言う。
 次に,アクセッサの例を2つのソースで示す。最初のソース(Accessor1.java,下図)では,クラスAが定義されて
おり,0以上でなくてはならないint型フィールドiを持つ(下図(1))。iはフィールドなのでprivate指定していることに
注意。クラスAには,フィールドiへアクセスするためのアクセッサが2つ用意されている。
 メソッドgetI()は,iの値を返してくれるゲッタである。このように,ゲッタの名前は「getフィールド名」という
形をしていることが多い。
 また,メソッドsetI()は,iに値を設定するセッタである。setI()は,渡された実引数の値をiに代入するが,実引数が
負の値である場合は,iの値を変更しない。このようにして,setI()は「フィールドiの値は0以上でなくてはならない」
という条件が常に成立するように,クラスAのオブジェクトを護っているのである。なお,このsetI()のようにセッタ
の名前は,「setフィールド名」という形になっていることが多い。
 下図のAccessor1.javaのmain()メソッドを見てみよう。(4)では,セッタsetI()を使い,A型オブジェクトaのフィー
ルドiの値を10に設定している。(5)では,ゲッタgetI()を使用してフィールドiの値を取り出して表示するので,10と
表示される。(6)では,またセッタsetI()を呼び出してフィールドiに-10を設定しようとしているが,setI()は負の値が
実引数に渡された場合にはiの値を変更しないので,(7)でiの値を表示しているところでは,やはり10と表示される。
Accessor1.java


 次に,今まで頻出してきたPersonクラスにprivate指定とアクセッサを追加した例を示す(Accessor2.java下図)。
ちゃんと,フィールドageとgenderはいずれもprivateになっている(下図(1),(2))。これらのフィールドへのアク
セッサが下図(3)〜(6)である。2つのセッタはいずれも実引数の値が適切なときだけフィールドに実引数の値を代
入している。talk()メソッドのなかでは,2つのセッタを利用してフィールドの値を取り出している(下図(7),(8))。
Accessor2クラスのmain()メソッドでは,2つのセッタを使って各フィールドの値を変更しているが,不正な値は
検知されて無視される(下図(9),(10))。
Accessor2.java



【計算機プログラムの実装】

RPNCalc.java (package宣言無しバージョン)


RPNCalcTest1.java (import宣言無しバージョン)


●実行の仕方
 List 4とList 5を同じフォルダ(ここでは,Z:\oopa)に入れて,List 5(RPNCalcTest1.java)をコンパイルする。
 すると,自動的にJavaコンパイラが「List 4にあるRPNCalc.java」も必要であることを
 分析して,自動的にList 4もコンパイルしてくれる。
 RPN計算の仕方は,1 2 + 3 4 - * を計算したい場合,*のかわりにxを使って以下のよう
 にする
  > java RPNCalcTest1 1 2 + 3 4 - x
 正しい答えが出たら,他にも色々計算してみよ。


【パッケージ】

 クラスは,小さい『モジュール』であった。関連するクラスをまとめて,『パッケージ』というより大きいモ
ジュールにまとめることができる。


●クラスのパッケージへの編入の仕方(package宣言)
 ソースファイルの先頭で
  package パッケージ名;
というようにパッケージ宣言を行う。サブパッケージに編入したい場合は,
  package パッケージ名.サブパッケージ名1;
とか,さらに階層の深いパッケージなら
  package パッケージ名.サブパッケージ名1.サブパッケージ名2;
などと宣言する。パッケージ宣言をすると,そのソースファイル内で定義されているクラスはすべて指定された
パッケージに編入される。
 パッケージ名は,
  ・javaそのものに付属しているものは java.〜javax.〜という名前
  ・個人や会社が作ったものは,会社名のURLの逆.〜という名前にすることになっている
   (例:com.kaishamei.〜)
※パッケージ宣言を行っていないソースファイルのクラスは,すべて名前のない特別な『デフォルトパッケージ』
 に編入される。

●別のパッケージ内のクラスを使う(import宣言)
 同一パッケージ内のクラスは特別に何もしなくても使用することが出来る。しかし,別のパッケージ内のクラス
を使うには,ソースファイルの先頭で
  import パッケージ名.クラス名1;
  import パッケージ名.クラス名2;
  import パッケージ名.クラス名3;
        …

という具合にimport宣言を行わなければならない。階層の深いパッケージの場合は,
  import パッケージ名.サブパッケージ名1.サブパッケージ名2.クラス名;
というように宣言することになる。基本的にimportしたい各クラスごとにimport宣言を書くのが良いとされている
が,実際にはなかなかたいへんになるので,
  import パッケージ名.*;
と書くと,そのパッケージ内の全クラスをいっぺんにimportすることができるようになっている。
 また,特にimport宣言をしなくても,java.langの下にあるクラスは自動的にimportされることになっている。

【パッケージとアクセス指定】

●クラスのアクセス指定
(ex.1) class A {} // アクセス指定無しクラス
(ex.2) public class A {} // publicクラス

  指定無しクラス publicクラス
同一パッケージ内のクラスから
別のパッケージ内のクラスから ×
※つまり,publicクラスへはどこからでもアクセスできる
※ひとつのソースファイルの中でpublicなクラスはひとつだけしか存在できない

●メンバのアクセス指定
  privateメンバ 指定無しメンバ publicメンバ
同一クラス内から
同一パッケージ内のクラスから ×
別のパッケージ内のクラスから × ×
※その他にprotectedというメンバアクセス指定があるがここでは省略する
※つまり,publicメンバはどこからでもアクセスできる

【パッケージとディレクトリ構成】

 パッケージに属するクラスは,パッケージ構成を反映したディレクトリの中になくてはならない。たとえば,List 4のRPNCalcクラスと
MyStackクラスを,com.learnjava2002.calculatorというパッケージにまとめる場合,「com」ディレクトリの中の「learnjava2002」
ディレクトリの中の「calculator」というディレクトリの中に,RPNCalc.classとMyStack.classを置かなくてはならない(Fig.10参照)。
 そのとき,comディレクトリを置くディレクトリ(名前は自由につけても良いが,Fig.10の例ではmyclassesという名前になっている)
を環境変数CLASSPATHに設定しなくてはならない。環境変数とは,オペレーティングシステムが用意しているもので,各種ソフトウェア
に必要な情報を設定しておくことが出来るものである。では,実際に下記の演習でList 4をパッケージ化してみよう。



●演習
 手順0)先ほど作成した
      RPNCalc.java,RPNCalc.class ,MyStack.class,RPNCalcTest1.class
     を削除せよ。これらは,パッケージ化するまえのものなので,もう必要がない。
     (というよりも,有ると以降の演習の邪魔になる)

 手順1)下図(Fig.10b)のように,「マイドキュメント」(Z:\MyDocuments)ディレクトリの下に,
     myclassesディレクトリ以下,calculatorディレクトリまでを作成せよ(綴りを間違えない
     ように気をつけること)


 手順2)RPNCalc.java (package宣言無しバージョン) を,上図のcalculatorディレクトリに保存し,
     下図(1)のようにpackage宣言を書き加えて保存し直せ。



今,状況は次のようになっている。



 手順3)下図(Fig.11)の(3)「大学のデスクトップPCのWindows XP」用の設定をせよ


 手順4)手順の3)で行った設定の変更を有効にするため,コマンドプロンプトを新しく起動せよ

 手順5)先ほどのRPNCalcTest1.java (import宣言無しバージョン) に,下図(1)のようにimport宣言を付けて,
     保存,手順4で開いたコマンドプロンプトでコンパイルせよ。

     コンパイルがうまくいくと,RPNCalc.classとMyStack.classがcalculatorディレクトリの中に出来てい
     るはずであるので,確認せよ(下図)。


     ここで起こったことを順序立てて書くと次のようになる。
      1)javaコンパイラがRPNCalcTest1.javaをコンパイルするとき,必要になるRPNCalcクラスが見つか
        らないので,CLASSPATHによりパッケージ階層の起点であるディレクトリ(この場合,
        Z:\MyDocuments\myclasses)を割り出す。
      2)次いで,パッケージ階層の起点ディレクトリ(myclasses)とimport宣言の内容から
          Z:\MyDocuments\myclasses\com\learnjava2002\calculator
        ディレクトリにクラスファイルRPNCalc.classがあるのではないかと推測する。
      3)しかし,そのcalculatorディレクトリ内には,コンパイル済みのRPNCalc.classはまだ無い。そこで
        javaコンパイラは,RPNCalcクラスを定義しているはずのソースファイルRPNCalc.javaを探す。そ
        うして,calculatorディレクトリ内にRPNCalc.javaを見つけたjavaコンパイラは,RPNCalc.javaを
        コンパイルしてcalculatorディレクトリの中に,RPNCalc.classとMyStack.classを生成したのである
        (上図の矢印)。

   手順6)手順の5でコンパイルしたRPNCalcTest1.classを実行してみよう。実行は例えば,
        > java RPNCalcTest1 1 2 + 3 4 - x
       とする。うまくいくと計算ができるはずである(実行に失敗する場合,ディレクトリ階層の名前,環境変
       数CLASSPATH,List 4のpackage宣言,List 5のimport宣言などにスペルミスがある可能性が高い)。
       ここで,javaコマンドがRPNCalcTest1.classを実行するとき,javaコマンドはRPNCalc.classが
       Z:\MyDocuments\myclasses\com\learnjava2002\calculator 内にあることを,環境変数CLASSPATH
       およびList 5のimport宣言を手がかりにして知り,
         RPNCalcTest1.class
         calculatorディレクトリ内のRPNCalc.class,MyStack.class
       をひとつのプログラムに結合して,実行しているのである。

【UML】
◎統一モデリング言語UML
 クラスやオブジェクトの関係などを図で表記する方法を,一般にオブジェクトモデリング表記法と呼びます。
 オブジェクト指向分析・設計の手法は,数多くの研究者・技術者グループが独自の方法論を展開し,オブジェクトモデリング表記法もそれぞれのグループが自らの方法論に合わせたものを使用していました。これらの多くは概念的には共通している部分が多かったにもかかわらず,実際の表記法や語彙の定義は異なっており,オブジェクトモデリングの技術論を扱う上で大きな障害になっていました。やがて統一された共通のオブジェクトモデリング表記法の必要性をうったえる声が高まり,統一モデリング言語UML(Unified Modeling Language)という標準的な表記法の策定が開始されました。そして,UMLのバージョン1.1(UML 1.1)が,1997年11月にOGM(Object Management Group)によって正式な標準として承認されました。

○クラスの表記

 UMLの一番基本的な図は,クラスを表すクラス図です。下図Fig.1にクラス図の基本的な表記法を示します。



まず,一般のクラスはFig.1-(1)のように,中身が3つに区分けされた長方形で示します。一番上の区画は“名前区画”と言い,その中にクラスの名前を書きます。一般にクラス名は典型的には大文字で始まり,区画の中央にボールド体で書かれます。
 中央の区画は,“属性区画”と言い,その中に属性を書き並べます属性(attribute)とは変数(フィールド)やfinal定数のことと思ってかまいません。重要でない属性は省略することができます。属性はFig.1-(2)に示す構文で属性区画に標準体・左詰めで書かれます。“可視性(visiblity)”は,その属性のアクセス特性でFig.1-(3)に示すように
 + … public
 ~ … package
 # … protected (未学習)
 - … private
の3つがあります。可視性は省略することはできますが,それは可視性がpublicであるとか未決定であるとかを示しているわけではないので,注意して下さい。
 “名前”はその属性の名前で,一般に小文字で始まります。“タイプ式”はその属性の型名と思ってかまいません。“プロパティ文字列”は,その属性の特性を明示するための文字列です。例えば,その属性が定数のように変更不可能なものであることを明示するときには,プロパティ文字列frozenを使用します。frozenはUMLで定められている標準のプロパティですが,必要に応じてユーザ定義のプロパティを導入してもかまいません。特にプロパティを明示する必要がなければ,{}ごと省略することが可能です。
 属性には,クラス属性とインスタンス属性があります。前者はクラス変数(staticなフィールド),後者はインスタンス変数(非staticなフィールド)に相当します。クラス属性には,“名前:タイプ式”の部分に下線を引き,インスタンス属性では下線を引きません。
 一番下の区画には,操作(operation)を書きます操作(operation)とはクラスまたはそのオブジェクトが持っている,なにかしらのサービスです。これに相当するプログラミング言語の文法要素は,“メソッド”です。重要でない操作は省略することができます。
 操作はFig.1-(4)に示す構文を使い,操作区画に標準体で左詰めに書かれます。“可視性”は属性のところで説明したものと同じです。操作の“名前”は典型的には小文字で始まるようにします。“仮引数リスト”部分は,各引数をFig.1-(5)で示す構文で表したものをカンマで区切って0個以上並べます。各仮引数を修飾する“種類”は
 in,out,inout
の3つのうちのいずれかです。これは仮引数が操作への入力として用いられるか,出力として用いられるかその両方か,に対応しています。デフォルトはinです。“引数リスト”と“返値のタイプ式”は必要に応じてともに省略することができます。
 操作にもプロパティを付加することができます。操作にもクラススコープ操作とインスタンススコープ操作があります。前者はクラスメソッド(staticなメソッド),後者はインスタンスメソッド(非staticなメソッド)に対応します。クラススコープ操作は,全体に下線を引いてその旨を明示します。
 あるクラスで操作の実装部が与えられている場合,その定義内容を明示したいときがあります。その場合,ノート(note)と呼ばれる,右上の角が折れた長方形を書き,その中に定義内容を書いて,破線でその定義を持つ操作に結びつけます((Fig.1-(1)の下部))。
 Fig.1-(1)のクラス図に対応するC++とJavaのソースコード例をそれぞれList 1,List 2および,下図に示します。なお,これらの例では操作は属性アクセッサのみですが,一般にはこのようなアクセッサは重要ではないので省略されます。