下準備
テストドライバの導入
第21回目 (秋学期08回目) 分のテストドライバを導入する。以下の手順で行う。
- ダウンロード のページを開く (ここをクリック)
- プロジェクト「java2007」にある「test」の左側の「+」をクリック
- ツリーが展開されるので「install-libraries.xml」を右クリック
- 「実行(R)」にマウスカーソルを合わせる
- 「1 Ant ビルド」をクリック
- 「コンソール」タブに"BUILD SUCCESSFUL"と表示されれば成功
- eclipseの画面でプロジェクト「java2007」を右クリック
- メニューが表示されるので、「更新」をクリック
- "week21.zip" をデスクトップなどにダウンロード
- eclipseの画面でプロジェクト「java2007」を右クリック
- メニューから「インポート(I)」を選択
- 「インポート」ウィンドウが表示されるので、「Zip ファイル」を選択
- 「次へ(N)」をクリック
- 宛先フォルダー(L): が「java2007」になっていることを確認
- From zip file: の右側にある 「参照(R)...」をクリック
- ファイルダイアログが表示されるので、ダイアログ内に表示されたダウンロードしたファイルをダブルクリック
- 前の画面に戻るので、From zip file: のエリアに正しいパスが入力されていることを確認
- フォルダ「/」の左にチェックがついていることを確認 (ついていなければチェックボックスをクリック)
- 「警告を出さずに既存リソースを上書き」にチェックがついていることを確認 (上書きしたくないファイルがある場合はチェックを外す)
- 「終了 (F)」をクリック
第21週目テストドライバの導入に成功すると、java2007 プロジェクトの test フォルダに j2.lesson08.xml というファイルが作成される。
パッケージの作成
過去の演習を参考にして、「j2.lesson08」というパッケージを作成する。
コンソール入力をファイルに書き出すプログラム
コンソールに入力した文字列をファイルに書き出すプログラムとして、ConsoleToFile クラスを作成する。
このプログラムは、書き込み先のファイルをまず指定し、そのあとに文字列を入力すると、最初に指定したファイルに入力された文字列を書き込む。文字列の入力を終了させるには、終了させたい位置でドット「.」のみを入力して改行すると、そこでコンソール入力が終了しそこまで (ドットのみの入力を除く) の文字列がファイルに書き出される。
ファイル名を入力:U:/sample.txt 内容を入力 (ドットのみの行で終了): this is sample サンプルテキスト . U:/sample.txtに書き出しました。
上記のように実行した後、U:\sample.txt の内容を見てみると次のようになっている。
this is sample サンプルテキスト
擬似コードの作成
「プログラム全体」の擬似コード
プログラム全体の擬似コードは以下のようにする。
プログラム全体 print "ファイル名を入力:" filename = コンソール入力 (String) writer = filename のファイルを書き込み用に開く print "内容を入力 (ドットのみの行で終了):", 改行 以下を繰り返し line = コンソールから一行入力 if line の内容が "." のみ ループを終了 writerにprint line, 改行 print filename + "に書き出しました", 改行
骨格の作成
クラスの作成
以下の手順で、パッケージ「j2.lesson08」に「ConsoleToFile」クラスを作成する。
- 先ほど作成したパッケージ 「j2.lesson08」の上で右クリック
- マウスカーソルを「新規」に合わせる
- 「クラス」をクリック
- クラス名は ConsoleToFile とする。
擬似コードの貼り付け (骨格のみ)
作成したクラスに、各擬似コードの名前を貼り付ける。ただし、「プログラム全体」ではファイルの出力やコンソール入力を行っているため、import java.io.*; もつける。
package j2.lesson08; import java.io.*; public class ConsoleToFile { // プログラム全体 }
パッケージについてしっかり理解できていれば、次のクラスを必要に応じて個別にインポートしてもよい。
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.FileWriter
- java.io.IOException
- java.io.InputStreamReader
mainメソッドの作成 (骨格のみ)
擬似コード「プログラム全体」に合わせて、クラス「ConsoleToFile」内にpublic static void main(String[] args) から始まるメソッドを作成する。ただし、擬似コード「プログラム全体」で使う API は IOException を発生させる可能性があるため、throws で指定しておく。
全体の骨格
ここまでのプログラムの骨格は以下のようになる。
package j2.lesson08; import java.io.*; public class ConsoleToFile { // プログラム全体 public static void main(String[] args) throws IOException { } }
骨格テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「ConsoleToFileに対する骨格テスト」を実行する。
骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
メッセージ | 詳細 |
---|---|
(クラス名), existence | j2.lesson08 に対象のクラスが存在していない。パッケージやクラス名を確認 |
(メソッド名), existence | 指定されたメソッドが存在しない |
(メソッド名), public | メソッドを作る際に public が抜けている |
(メソッド名), static | メソッドを作る際に static が抜けている |
(メソッド名), type <T> | メソッドを作る際に戻り値の型を間違えている (正しくは <T>) |
(メソッド名), throws java.io.IOException | メソッドを作る際に throws IOException が抜けている |
プログラムへの変換
先ほど作成した ConsoleToFile クラスの main メソッドの中身を記述する。
まずは擬似コードをコメントとして貼り付ける。
// プログラム全体 public static void main(String[] args) throws IOException { // print "ファイル名を入力:" // filename = コンソール入力 (String) // writer = filename のファイルを書き込み用に開く // print "内容を入力 (ドットのみの行で終了):", 改行 // 以下を繰り返し // line = コンソールから一行入力 // if line の内容が "." のみ // ループを終了 // writerにprint line, 改行 // print filename + "に書き出しました", 改行 }
コンソール入力
擬似コードの下記の部分は、今までどおり入力を要求する文章を表示し、コンソール入力を行うだけなので問題ない。
print "ファイル名を入力:" filename = コンソール入力 (String)
これまでの知識で擬似コードを簡単に変換できる。
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // print "ファイル名を入力:" System.out.print("ファイル名を入力:"); // filename = コンソール入力 (String) String filename = reader.readLine();
ファイルを開く処理
擬似コードの次の部分では、書き込み先のファイルを開いている。
writer = filename のファイルを書き込み用に開く
ファイルを操作する処理は一般に低速である。ファイルを扱う場合は、必ずバッファ付き入出力とセットで考えるようにするとよい。
ファイルに文字列を書き込むためのクラスは java.io.FileWriter で、バッファつきの文字列出力を行うためのクラスは java.io.BufferedWriter である。
そこで、次のようなプログラムを書ける。
BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
これで、書き込み用のバッファを持ったファイル出力のためのインスタンスを作成できた。
ファイルを開くコードを書いたら、すぐにファイルを閉じるコードを書くとよい。そのような癖をつけるとファイルを閉じる処理を書き忘れずにすむ。
ファイルを操作するプログラムは、IOException を発生させるものが多い。そのため、try-finally の形で書かないと、ファイルを閉じる前に例外が発生してファイルを閉じることができなくなってしまう。
ファイルを開く際には、次のようなパターンがあることを紹介した。
(ファイルを開く処理) try { (ファイルを扱う処理) } ... (必要なだけ catch 節) finally { (ファイルを閉じる処理) }
今回は、catch 節で IOException を捕捉しないことにして、次のように書く。
// writer = filename のファイルを書き込み用に開く // # BufferedWriter を使う BufferedWriter writer = new BufferedWriter(new FileWriter(filename)); // # ファイルを開いたので、try-finally で閉じる try { } finally { writer.close(); }
これより先は、try 節の中にプログラムを記述することになる。
入力された文字列をファイルに書き出す処理
擬似コードの次の部分では、入力された文字列をファイルに書き出している。
print "内容を入力 (ドットのみの行で終了):", 改行 以下を繰り返し line = コンソールから一行入力 if line の内容が "." のみ ループを終了 writerにprint line, 改行
この部分は、前回の演習の FileToConsole で非常に似ているプログラムを書いていた。
// 以下を無限に繰り返す while (true) { // print "数値を入力:" System.out.print("数値を入力:"); // input = コンソール入力 (文字列) String input = reader.readLine(); // input が空の文字列なら繰り返しをやめる if (input.equals("")) { break; } // double number = input を数値に変換したもの double number = Double.parseDouble(input); // list に number を追加 list.add(new Double(number)); }
これを参考にすると、途中まで簡単に書ける。
// print "内容を入力 (ドットのみの行で終了):", 改行 System.out.println("内容を入力 (ドットのみの行で終了):"); // 以下を繰り返し while (true) { // line = コンソールから一行入力 String line = reader.readLine(); // if line の内容が "." のみ if (line.equals(".")) { // ループを終了 break; } // writerにprint line, 改行 }
最後の「writerにprint line, 改行」は、今までの擬似コード print と異なり、ファイルへ文字列を書き出している。そのため、「System.out.println」は使うことができない。
ファイルに文字列を出力するには、BufferedWriter (FileWriter) クラスの write(String) メソッドを使えばよい。
// writerにprint line, 改行 writer.write(line); writer.newLine();
BufferedWriter には改行文字を出力するメソッド newLine メソッドがある。細かい話になるが、Windows で言う「改行」と他のオペレーティングシステムで言う「改行」は厳密には違う文字列で表されることがある。そのため、"\n"では必ずしも「正しい改行」を表すことができない。そこで、この newLine メソッドを呼び出すと、対象の Writer に「正しい改行」を書き込んでくれる。
まとめると、繰り返し部分は次のようになる。
// print "内容を入力 (ドットのみの行で終了):", 改行 System.out.println("内容を入力 (ドットのみの行で終了):"); // 以下を繰り返し while (true) { // line = コンソールから一行入力 String line = reader.readLine(); // if line の内容が "." のみ if (line.equals(".")) { // ループを終了 break; } // writerにprint line, 改行 writer.write(line); writer.newLine(); }
完了を通知する処理
説明するまでもないが、繰り返しを抜けたら最後に完了したことを通知している。
print filename + "に書き出しました", 改行
次のように書ける。
// print filename + "に書き出しました", 改行 System.out.println(filename + "に書き出しました。");
ファイルを閉じる処理
最後にファイルを閉じるが、これはファイルを開く際に一緒に記述してしまったので特にすべきことはない。
プログラムの実行
プログラムを実行する。
入力に "U:/pr01c2f.txt"と与えて、その後に次のような入力を行う。
Hello, I/O! こんにちは、入出力! .
すると、コンソールには次のように表示される。
ファイル名を入力:U:/pr01c2f.txt 内容を入力 (ドットのみの行で終了): Hello, I/O! こんにちは、入出力! . U:/pr01c2f.txtに書き出しました。
そして、Uドライブに pr01c2f.txt が作成されて、そのファイルの内容は次のようになっている。
Hello, I/O! こんにちは、入出力!
機能テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「ConsoleToFileに対する機能テスト」を実行する。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
メッセージ | 詳細 |
---|---|
期待された結果と異なります | 出力された結果が期待された値と異なる。 |
機能テストの項目
項目名 | テストの内容 |
---|---|
sample | .temp/pr01.txt に、サンプルと同じ内容を出力 (終了後に削除している) |
プログラム全体
ConsoleToFileクラス全体を掲載しておく。
package j2.lesson08; import java.io.*; public class ConsoleToFile { // プログラム全体 public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // print "ファイル名を入力:" System.out.print("ファイル名を入力:"); // filename = コンソール入力 (String) String filename = reader.readLine(); // writer = filename のファイルを書き込み用に開く // # BufferedWriter を使う BufferedWriter writer = new BufferedWriter(new FileWriter(filename)); // # ファイルを開いたので、try-finally で閉じる try { // print "内容を入力 (ドットのみの行で終了):", 改行 System.out.println("内容を入力 (ドットのみの行で終了):"); // 以下を繰り返し while (true) { // line = コンソールから一行入力 String line = reader.readLine(); // if line の内容が "." のみ if (line.equals(".")) { // ループを終了 break; } // writerにprint line, 改行 writer.write(line); writer.newLine(); } // print filename + "に書き出しました", 改行 System.out.println(filename + "に書き出しました。"); } finally { writer.close(); } } }
ファイルの内容をコンソールに書き出すプログラム
ファイル内の文字列をコンソールに表示するプログラムとして、FileToConsole クラスを作成する。
このプログラムは、読み込み元のファイルを指定し、そのファイルの内容をコンソールに表示する。
先ほどの演習の実行例で作成したファイルを読み込むと、次のようになる。
ファイル名を入力:U:/pr01c2f.txt Hello, I/O! こんにちは、入出力!
また、このプログラムでは、読み込もうとしたファイルが存在しなかった場合にファイルが開けなかったことを表示してプログラムを終了させる。例えば、ファイル名を入力する場面で存在しないファイルを指定した場合、次のようになる。
ファイル名を入力:U:/notfound.txt U:/notfound.txtが開けませんでした。
擬似コードの作成
「プログラム全体」の擬似コード
プログラム全体の擬似コードは以下のようにする。
プログラム全体 print "ファイル名を入力:" filename = コンソール入力 (String) file = filename のファイルを読み込み用に開く if filename が開けない print filename + "が開けませんでした。", 改行 プログラムを終了 以下を繰り返す line = ファイルから一行読み込む if file がファイルの終端に達したら 繰り返しを終了 print line, 改行
骨格の作成
クラスの作成
以下の手順で、パッケージ「j2.lesson08」に「FileToConsole」クラスを作成する。
- 先ほど作成したパッケージ 「j2.lesson08」の上で右クリック
- マウスカーソルを「新規」に合わせる
- 「クラス」をクリック
- クラス名は FileToConsole とする。
擬似コードの貼り付け (骨格のみ)
作成したクラスに、各擬似コードの名前を貼り付ける。ただし、「プログラム全体」ではファイルやコンソールからの入力を行っているため、import java.io.*; もつける。
package j2.lesson08; import java.io.*; public class FileToConsole { // プログラム全体 }
import は個別に行ってもよい。
mainメソッドの作成 (骨格のみ)
擬似コード「プログラム全体」に合わせて、クラス「FileToConsole」内にpublic static void main(String[] args) から始まるメソッドを作成する。ただし、擬似コード「プログラム全体」で使う API は IOException を発生させる可能性があるため、throws で指定しておく。
java.io.FileNotFoundException に関しては、以下の擬似コードの部分で捕捉するため、throws には指定しない (そもそも、IOException の子クラスであるため throws で指定しなくても関係ない)。
if filename が開けない print filename + "が開けませんでした。", 改行 プログラムを終了
全体の骨格
ここまでのプログラムの骨格は以下のようになる。
package j2.lesson08; import java.io.*; public class FileToConsole { // プログラム全体 public static void main(String[] args) throws IOException { } }
骨格テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「FileToConsoleに対する骨格テスト」を実行する。
骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
メッセージ | 詳細 |
---|---|
(クラス名), existence | j2.lesson08 に対象のクラスが存在していない。パッケージやクラス名を確認 |
(メソッド名), existence | 指定されたメソッドが存在しない |
(メソッド名), public | メソッドを作る際に public が抜けている |
(メソッド名), static | メソッドを作る際に static が抜けている |
(メソッド名), type <T> | メソッドを作る際に戻り値の型を間違えている (正しくは <T>) |
(メソッド名), throws java.io.IOException | メソッドを作る際に throws IOException が抜けている |
プログラムへの変換
先ほど作成した FileToConsole クラスの main メソッドの中身を記述する。
まずは擬似コードをコメントとして貼り付ける。
// プログラム全体 public static void main(String[] args) throws IOException { // print "ファイル名を入力:" // filename = コンソール入力 (String) // file = filename のファイルを読み込み用に開く // if filename が開けない // print filename + "が開けませんでした。", 改行 // プログラムを終了 // 以下を繰り返す // line = ファイルから一行読み込む // if file がファイルの終端に達したら // 繰り返しを終了 // print line, 改行 }
コンソール入力
擬似コードの下記の部分は、今までどおり入力を要求する文章を表示し、コンソール入力を行うだけなので問題ない。
print "ファイル名を入力:" filename = コンソール入力 (String)
これまでの知識で擬似コードを簡単に変換できる。
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // print "ファイル名を入力:" System.out.print("ファイル名を入力:"); // filename = コンソール入力 (String) String filename = reader.readLine();
ファイルを開く処理
擬似コードの次の部分では、読み込み元のファイルを開いている。
file = filename のファイルを読み込み用に開く
先ほども説明したが、ファイルを操作する処理は一般に低速である。ファイルを扱う場合は、必ずバッファ付き入出力とセットで考えるようにするとよい。
ファイルから文字列を読み込むためのクラスは java.io.FileReader で、バッファつきの文字列出力を行うためのクラスは java.io.BufferedReader である。また、BufferedReader を使うことにより、readLine() メソッドという一行読み出すメソッドも使用できるようになる。
次のようなプログラムを書ける。
// file = filename のファイルを読み込み用に開く BufferedReader file = new BufferedReader(new FileReader(filename));
先ほどと同様に、ファイルを閉じる処理も一緒に書いてしまう。
// file = filename のファイルを読み込み用に開く BufferedReader file = new BufferedReader(new FileReader(filename)); // # 開いたので閉じる try { } finally { file.close(); }
ファイルを操作する場合は、try 節の中にプログラムを書くことになる。
ファイルが開けなかった場合の処理
擬似コードの次の部分では、開こうとしたファイルが開けなかった場合の処理を記述している。
if filename が開けない print filename + "が開けませんでした。", 改行 プログラムを終了
Java で FileReader を使うと、if 文でファイルが開けたかどうかを確認することはできない。ファイルが開けなかった場合はファイルを開く際に java.io.FileNotFoundException が発生するので、これを捕捉してやればよい。そのため、
(ファイルを開く処理) try { (ファイルを扱う処理) } ... (必要なだけ catch 節) finally { (ファイルを閉じる処理) }
というパターンのうち、(ファイルを開く処理) の部分を書き換える。
BufferedReader file; try { file = new BufferedReader(new FileReader(filename)); } // if filename が開けない catch (FileNotFoundException e) { // print filename + "が開けませんでした。", 改行 System.out.println(filename + "が開けませんでした。"); // プログラムを終了 return; } // # 開いたので閉じる try { // # ... 以降はファイルを扱う処理 (省略)
上記のように、ファイルを扱う処理とは別の try 節を用意して、ファイルが開けなかった場合の処理を記述する。
ファイルの内容をコンソールに出力する処理
擬似コードの次の部分では、入力された文字列をファイルに書き出している。
以下を繰り返す line = ファイルから一行読み込む if file がファイルの終端に達したら 繰り返しを終了 print line, 改行
先ほど作成した ConsoleToFile クラスでは、非常に似たプログラムを書いていた。
// 以下を繰り返し while (true) { // line = コンソールから一行入力 String line = reader.readLine(); // if line の内容が "." のみ if (line.equals(".")) { // ループを終了 break; } // writerにprint line, 改行 writer.write(line); writer.newLine(); }
「line = コンソールから一行入力」と「line = ファイルから一行読み込む」は、どちらも 何らかの入力装置が接続されている BufferedReader から一行読み込むという処理である (前者はコンソールで、後者はファイル)。そのため、一行読み込む部分は reader を file に変更してやることで、読み込む先をコンソールからファイルに変更できる。
// 以下を繰り返す while (true) { // line = ファイルから一行読み込む String line = file.readLine(); // if file がファイルの終端に達したら // 繰り返しを終了 // print line, 改行 }
また、ファイルの終端に達した場合は、readLine() メソッドが null という値を返す。そのため、読み込んだ内容が null ならば繰り返しを抜けてやればよい。
// 以下を繰り返す while (true) { // line = ファイルから一行読み込む String line = file.readLine(); // if file がファイルの終端に達したら if (line == null) { // 繰り返しを終了 break; } // print line, 改行 System.out.println(line); }
一気に書いたが、「print line, 改行」は今までどおりなので特に解説しない。
もし、ここで発生した例外を捕捉したい場合、プログラム中の try-finally の間に catch (IOException e) {} という例外処理を記述するブロックを作成してやればよい。例えば、次のように書けばよい (演習では下記のようにしなくてもよい)。
try { while (true) { ... } } catch (IOException e) { System.out.println(filename + "の読み込み中にエラーが発生"); return; } finally { file.close(); }
ファイルを閉じる処理
最後にファイルを閉じるが、これはファイルを開く際に一緒に記述してしまったので特にすべきことはない。
プログラムの実行
プログラムを実行する。
入力に先ほど作成したファイル "U:/pr01c2f.txt"と与えると、次のように表示される。
ファイル名を入力:U:/pr01c2f.txt Hello, I/O! こんにちは、入出力!
他にも、このプログラムを使って「このプログラム自体」を読み出すこともできる。
このプログラムは、src ディレクトリの j2/lesson08/FileToConsole.java に保存されている。ファイル名として "src/j2/lesson08/FileToConsole.java" を指定してやることにより、このプログラム自体を表示することができる。
ファイル名を入力:src/j2/lesson08/FileToConsole.java package j2.lesson08; import java.io.*; public class FileToConsole { (..以下略)
Windows 上では、U:/ のようにドライブ名からファイルの位置を指定することを「絶対パス指定」と呼び、現在いるディレクトリ (Eclipse のプロジェクト) から相対的なファイルの位置を指定することを「相対パス指定」と呼ぶ。上記のように絶対パス指定もも相対パス指定も、自動的に判別してファイルを探してくれる。
機能テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「FileToConsoleに対する機能テスト」を実行する。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
メッセージ | 詳細 |
---|---|
期待された結果と異なります | 出力された結果が期待された値と異なる。 |
機能テストの項目
項目名 | テストの内容 |
---|---|
sample | .temp/pr02.txt を実行例と同じ内容で作成し、それを読み出す (終了後に削除している) |
プログラム全体
FileToConsole 全体を記述しておく。
package j2.lesson08; import java.io.*; public class FileToConsole { // プログラム全体 public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // print "ファイル名を入力:" System.out.print("ファイル名を入力:"); // filename = コンソール入力 (String) String filename = reader.readLine(); // file = filename のファイルを読み込み用に開く BufferedReader file; try { file = new BufferedReader(new FileReader(filename)); } // if filename が開けない catch (FileNotFoundException e) { // print filename + "が開けませんでした。", 改行 System.out.println(filename + "が開けませんでした。"); // プログラムを終了 return; } // # 開いたので閉じる try { // 以下を繰り返す while (true) { // line = ファイルから一行読み込む String line = file.readLine(); // if file がファイルの終端に達したら if (line == null) { // 繰り返しを終了 break; } // print line, 改行 System.out.println(line); } } finally { file.close(); } } }