第6講:入力された「時間割」をファイルに保存する

その1 入力された「1日分の時間割」をファイルに出力・

    保存する「ClassTableWriter」を作成する

 これまでに作成した「ClassInfo」は、折角キーボードから入力した「1日分の時間割」をプログラムが実行されている間しか保持することができませんでした。できれば一度入力したデータは後々使えるように保存しておきたいものです。またデータの入力とその利用はこのシステムの場合本来別の人間が行うべきことですが、それを実現するにはデータの恒常的な保存方法を学ぶ必要があります。ということで、今回は一旦キーボードから入力された「1日分の時間割」を、ファイルとして出力し保存するする方法を学びます。

(1)「1日分の時間割」のファイル出力

 前回までの学習によって、当初の定義通りに5つの「授業の情報」を「1日分の時間割」としてプログラム上で表現することができるようになりました。ただしコンピュータが入力された「1日分の時間割」を記憶できるのは「ClassInfo」のメインメソッドを実行している間のみで、実行が終わると同時に入力されたデータは破棄されてしまいます。プログラムを起動するごとに新たにデータの入力をしなければならないようでは、実用性のあるシステムとはいえません。

 そこで今回は、キーボードから入力されVectorにまとめられた「1日分の時間割」をファイルとして出力し、コンピュータのハードディスクに記憶させる方法を学びます。このようにしておけば、後に学ぶファイルからの情報の入力によって情報の再利用を行うことが可能になります。これから先は、この出力されるファイルのことを「データファイル」と呼びます。

(2)データファイル作成専用クラス「ClassTableWriter」の設計

 ここで一旦「ClassInfo」の設計を見直してみましょう。このプログラムには、「「1日分の時間割」の入力」(データの入力)と「希望する時限の「授業の情報」の表示」(データの利用)という二つの機能がありました。そして、事実上これらを同一人物が行うようなプログラムとなっていました。情報のファイル保存を知らない段階では致し方のないことだったのですが、本来これらの機能は別々のプログラムに分けておくべきものです。

 そこで、今回から「「1日分の時間割」の入力」「入力された情報のファイル出力」のふたつの機能からなるデータファイル作成の専用クラス「ClassTableWriter」を新たに定義・作成したいと思います。これは後に「時間割表示システム」を完成する際に、システムが扱う1週間分の「時間割」のデータファイルを作成するクラスとして拡張してゆくものの原型です。このようなクラスをもうけることで、今度はデータの利用だけを行うクラスを考えることもできるようになります。

 ここでデータの入出力という観点から、従来の「ClassInfo」とこれから作成する「ClassTableWriter」の相違点を図でまとめておきます。
ClassInfoの場合
キーボードから →
データ入力

ClassInfo
 → ディスプレイへ
 データ出力
ClassTableWriterの場合
キーボードから →
データ入力

ClassTableWriter
→ ファイルへ
  データ出力
 また「ClassTableWriter」の出現により、以下のようなクラスを想定することができる。
 ファイルから →
データ入力

データの利用専用クラス
 → ディスプレイへ
 データ出力

 ということで、この「ClassTableWriter」の設計を考えてみます。このクラスは後に拡張を行う可能性があるので、すべての処理をメイン・メソッドに記述するのではなく、あらかじめ属性、メソッドを考えた上で、実行されたときの具体的な手順をあとからメイン・メソッドに記述することにします。

 まずこのクラスを作成する目的ですが、これはキーボードから読み込んだ「1日分の時間割」をファイルとして出力すること、とはっきり定義しておきます。この目的からまず属性(=そのクラスを成立させるデータの項目)を考えます。すると、おそらくまず考えられるのは「1日分の時間割」です。

 属性として取り上げるデータの項目は、通常、クラスを構成するメソッドのうち複数のものに関連する可能性のあるものを選びます。ある一つのメソッドが行う処理にだけ関係する(つまりクラス全体の機能に関わらない)データはとりあげません。したがってこのクラス全体に関わるものをあげるとすれば「1日分の時間割」以外にはなさそうです。

 またメソッドについても今のところ「キーボードから「1日分の時間割」を読み込むメソッド」と「「1日分の時間割」をファイルとして出力するメソッド」の二つがあれば良さそうです。そしてメイン・メソッドでは、これらの属性・メソッドを用いながら実際にファイルを出力するまでの処理手順を記述します。

 それでは、それぞれについてより具体的に考えてみます。これらをヒントにプログラムの記述を考えてみてください。「メソッド2」をのぞけば既知の記述で書けるはずです。

属性1「1日分の時間割」
 これはプログラム上では、5つの「授業の情報」を要素として格納するVectorのインスタンスとして表現されました。属性を記述する際にはVectorという「型名」とインスタンス名(今回は「ClassInfo」のものを引き継いで「day」とする)だけを記述しておきます。「授業の情報」のデータの読み込みは、メインメソッドが起動され処理が実行されるときに行われます。
>>記述例

   

メソッド1「キーボードから「1日分の時間割」を読み込むメソッド」
 このメソッドはとりあえず「readData」という名前で呼ぶことにしましょう。必要となる処理の手順については、「ClassInfo」のメイン・メソッドを参考にすることができます。基本的には次のような手順を取ればよいでしょう。
 1)属性として記述された「Vector型のインスタンスday」のインスタンス化をする 2)キーボード入力の準備(BufferedReaderのインスタンス化)をする
 3)5つの「授業の情報」の値をキーボードから読み込み「day」に加える
なお、引数をとることは今回は考えないことにします。
>>記述例

メソッド2「「1日分の時間割」をファイルとして出力するメソッド」
 メソッド名は「fileOut」にします。このメソッドの処理手順は、のちほど説明します。なお、このメソッドも引数を取らないことにしておきます。

メイン・メソッド
 もしこの「ClassTableWriter」の二つのメソッドがきちんと機能するならば、メイン・メソッドですべきことは以下の三つです。
 1)ClassTableWriter(つまり自分自身)をインスタンス化する
 2)ClassTableWriterのreadDataメソッドを起動する
 3)ClassTableWriterのfileOutメソッドを起動する
>>記述例

(3)データのファイル出力の方法

 Javaでデータの入出力の命令を記述する際には、「〜Stream」という名前の付いたクラスがしばしば登場します。このStreamとはJavaにおけるデータの(入出力の)流れを表す概念です。

 今回ファイル出力の際に一般的に用いられるのは「FileOutputStream」というクラスです。そして今回ファイルを構成するデータとして出力されるのはVectorのインスタンスですが、インスタンスを出力するためには「ObjectOutputStream」を利用します。どちらも「java.ioパッケージ(BufferedReaderのときに利用)」に用意されています。これらを利用する記述のしかたを説明する前に、Vectorのインスタンスがファイルとして出力されるまでのデータの「Stream(流れ)」を概念的に整理しておきます。

Vectorのインスタンス>-- OOS -→ >-- FOS -→ ファイル
 但しOOS は ObjectOutputStream, FOS は FileOutputStream を表す。

 これから記述する命令は、上記のようにVectorのインスタンスを「ObjectOutputStream→ FileOutputStream」という順に経由させてファイルとして出力する命令です。

 まず最初に出力という点で「外側」にあたる「FileOutputStream」をインスタンス化します。

FileOutputStream インスタンス名 = new FileOutputStream("ファイル名");

 もうおなじみの記述ですね。ただし、インスタンス化の際にこのクラスは「引数」を要求します。それが「"ファイル名"」です。この部分には文字列型データで、FileOutputStreamから出力されるファイルのファイル名を渡します。

 「FileOutputStream」のインスタンス化がすんだあとで、「ObjectOutputStream」をインスタンス化します。このインスタンス化の際に「引数」として、先ほどインスタンス化した「FileOutputStream」のインスタンス名を記入します。

ObjectOutputStream  インスタンス名 
   = new ObjectOutputStream(FOSのインスタンス名);

 ということで、この順序で二つのクラスのインスタンス化がすんだなら、つぎに、「ObjectOutputStream」の「writeObject」メソッドを利用してファイル出力を実行します。ここでで仮に「ObjectOutputStream」のインスタンス名を「oos」とすると、

oos.writeObject(出力するインスタンス名);

 このように引数の部分にファイルとして出力したいインスタンス名を記入します。この命令が実行されて、初めてインスタンスの内容がファイルとして出力されるのです。もちろんのこのケースにおいて出力したいインスタンスとは「1日分の時間割」をしまったVectorインスタンス、つまり属性として定義した「day」のことです。

 ちなみにこれらの記述はすべてエラー発生の可能性があるため「try{}catch(){}」の中で行います。

 これで「ClassTableWriter」の「fileOut」メソッドの中身はすべて説明済みです。もう一度まとめると、

 1)FileOutputStreamをインスタンス化する

 2)ObjectOutputStreamをインスタンス化する

 3)ObjectOutputStreamのwriteObjectメソッドを起動する

という手順になります。ちなみに今回の「fileOut」メソッドでは、FileOutputStreamから出力するファイルのファイル名は、毎回同じ「Day.data」になるようにしておきます。それでは、「ClassTableWriter」を作成してみて下さい。

>>解答例のプログラム

(4)ClassTableWriterを実行する前に(オブジェクトのシリアライズ化について)

 ClassTableWriterがうまく作成できたら、早速実行してみる前に一つだけしておくことがあります。

 Javaでは、先ほど説明したStreamを利用してあるクラスのインスタンスを送受信(あるいは入出力)する場合、そのインスタンスは必ずシリアライズ化(直列化)可能であると定義されていなければなりません。シリアライズ化とは、一言でいえばデータを送信するための準備であるといえます。インスタンスは属性やメソッドなどに関するデータのかたまりですが、それを丸ごと「かたまり」のままOutputStreamへ送信することはできません。シリアライズ化をすることで、一旦「かたまり」を構成要素へとバラバラに分解し、一つずつ順に送信します。

 シリアライズされたインスタンスがInputStream(Javaにおける入力の流れ。次の「その2」で扱います)から受信される際には、このバラバラの構成要素は元のインスタンスのかたちへと復元されます。

 今回直接送受信するのはVectorクラスのインスタンスです。Vectorクラス自体はあらかじめシリアライズ化可能と定義されており、問題ありません。しかし今回の場合、Vectorのインスタンスには「授業の情報」、すなわちClassTableクラスのインスタンスが格納されています。こういった場合、ClassTableのインスタンスもシリアライズ化可能でなくてはなりません。

 そこで、次の手順でClassTableをシリアライズ化可能なクラスにします。ClassTableのプログラムのクラス名を宣言する行に、以下の赤字の記述を加えて下さい。

public class ClassTable implements Serializable{

 この「implements Serializable」を加え、そのあとでClassTableをコンパイルし直せば、それだけでこのクラスはシリアライズ化可能となります。

 これでやっとClassTableWriterを実行する準備が整いました。早速実行してみて下さい。


リンク:

<全体のトップページ>

     |

<第6講のトップページ>

     |-------------------------|

<第6講・その1>        <第6講・その2>