プログラミング入門(金曜1・2限)講義資料(2008年後期)

プログラミング入門 第7回

前回の復習

クラスとは(その2)

Javaのプログラムを書くときは,こんな風に書きはじめていました.

public class JavaProgram {
    public static void main(String[] args) {

これは,JavaProgramというクラスを作成し,mainという名前のクラスメソッドを定義していたのです.

args はStringの配列で,Javaプログラムを実行させるときに与えられた引数が入っています.Javaプログラムを実行させるときに,次のように後ろに引数をつけると,

% java JavaProgram ABC DEF GHI

args[0] = "ABC", args[1] = "DEF", args[2] = "GHI" という文字列が格納されています.

以下は与えられた引数全てを表示するプログラムです.コマンドラインから java PrintArg ABC DEF GHI のように,後ろに引数をつけて実行してみてください.配列 args に引数が入っていることが確認できるでしょう.


このファイルをダウンロード ■ UNIX用(EUC版) ■ Windows用(SJIS版)
(上のどちらかのリンクを右ボタンでクリックして「リンク先を名前をつけて保存」して下さい)

public class PrintArg {
    public static void main(String[] args) {
	for (int i = 0 ; i < args.length ; i++) {
	    System.out.println(args[i]);
	}
    }
}


Stringの配列は,実際には以下のように格納されています.

インスタンス変数

さて,配列argsの長さは args.length に入っているのでした.これは,メソッド呼び出しに良く似ていますが,最後にカッコがないのでメソッド呼び出しではありません.

クラスによっては,オブジェクト毎に変数を提供している場合があります.これをインスタンス変数と呼びます.配列オブジェクトは,length という int 型のインスタンス変数を持っていて,常に配列の長さが入っています.インスタンス変数はオブジェクト毎に存在します.つまり,複数の配列オブジェクトがある場合,それぞれのオブジェクト毎に別々のインスタンス変数が存在するわけです.

インスタンス変数にアクセスするには次のように書きます.

   オブジェクト変数 . インスタンス変数名

クラス変数

メソッドにインスタンスメソッドとクラスメソッドがあったように,インスタンス変数があるならばクラス変数もあります.

クラス変数は,特定のオブジェクトに関連しない値を保持しています.例えば,Mathクラスには円周率を保持している PI というクラス変数があります.円周率は,オブジェクト毎に変化させる必要はないので,クラス変数として提供されているのです.クラス変数はクラスに対して1つだけ存在します

クラス変数にアクセスするには次のように書きます.

   クラス名 . クラス変数名

つまり,double area = Math.PI * r * r; のように書けます.

APIドキュメントでは,インスタンス変数やクラス変数は「フィールド」という項目に列挙されています.staticと書いてあるものはクラス変数で,staticがないものはインスタンス変数です.

今までSystem.out.printlnと呪文のように書いてきましたが,実はこれはSystemクラスのoutというクラス変数に対して,printlnというインスタンスメソッドを呼び出していたのです.outはjava.io.PrintStream型のオブジェクト変数で,標準出力(普通は画面)を表しています.

ファイルからの読みこみ

多くの実用的なプログラムでは,ファイルを読み書きが不可欠です.プログラミング言語では,ファイルはどのように取り扱うのでしょうか?

ファイルの構造

以下のような3行のテキストファイルを考えてみます.

ABC
012
あいう

画面上ではこのように見えていますが,ファイルには実際にはバイトの列で記録されています(バイト = 8ビット = 2進数8桁.10進数で0〜255).

上記のファイルを数値としてみると以下のようになります(10進数).

65 66 67 10 48 49 50 10 164 162 164 164 164 166 10

最初の 65 は,"A" を表す文字コードです.アルファベットや数字等は,1文字を1バイトで表すASCIIコードが使われています.

3個所にある10は,改行の位置を示しています.改行コードと呼ばれます.改行コードの10もASCIIコードで決まっています

"あ"は 164 162 という2バイトで表されています.これは日本語EUCコードと呼ばれる文字コード体系です.EUCコードはASCIIコードと混在して使えるようになっています.

また,(あたりまえのことですが)ファイルは有限の長さを持っています.つまり,ファイルには終わりがあるわけです.

Javaでのファイルの取り扱い方

Javaでは,文字は Unicode で扱います.UnicodeとASCIIコードや日本語EUCコードは異なるコード体系なので,Javaでファイルを扱うためにはUnicodeとの間で変換する必要があります.

Javaではファイルもオブジェクト指向で取り扱います.ファイルをあるクラスのオブジェクトと結び付けて,そのクラスのメソッドを使うことでファイルを扱うのです.

Unicodeとの変換もクラスが自動的にやってくれます.

ファイルの読み書きに使うクラスは java.io パッケージにあります.このため,これらのクラスを使うためには import java.io.*; という宣言が必要です.

FileReaderクラス

java.io.FileReaderクラスは,テキストファイルを1文字ずつ読み込むためのクラスです.自動的にファイルのコード(EUCコード等)からUnicodeへのコード変換も行います.

以下のようにして,指定したファイルをオープンし,読み込めるようにします.

  FileReader fin = new FileReader("ファイル名");

このクラスを直接使うと効率が悪いので,普通は次のBufferedReaderクラスを使います.

BufferedReaderクラス

java.io.BufferedReaderクラスを使うと,ファイルの1行を文字列として取得できます.これはreadLineメソッドによって行います.readLineメソッドは呼ばれるとファイルを先頭から1行ずつ,次々にStringオブジェクトとして返します.

BufferedReaderにはファイルをオープンする機能はありません.BufferedReaderはFileReaderオブジェクトを使って読み込みを行います.このため,BufferedReaderオブジェクトは次のようにして作ります.コンストラクタの引数には,FileReaderオブジェクトへの参照を渡します.

  // ファイルのオープン
  FileReader fin = new FileReader("ファイル名");
  BufferedReader in = new BufferedReader(fin);

  String s = in.readLine(); // 最初の1行を s に入れる
  System.out.println("最初の行は" + s + "です");
  // ファイルのクローズ
  in.close();

これでもよいのですが,FileReaderオブジェクトはBufferedReaderオブジェクトを作るときにしか使わないので,最初の部分は下のように書くのが普通です.

  // ファイルのオープン
  BufferedReader in = new BufferedReader(new FileReader("ファイル名"));

また,このクラスはバッファリングも行います(だからBufferedReaderなのです).一般に,ディスクから読み取る速度はコンピュータの計算速度に比べると非常に遅いので,ファイルを予めメモリに読み込んでおいて,必要になる度にメモリから取り出してくるということを行います.これがバッファリングと言う技法です.この方法によって読み込みの速度を高速化しています.

ファイルのクローズ

ファイルを読んだ後は,クローズという操作が必要です.クローズによって,オブジェクト(とオペレーティングシステム)に対して「もうこのファイルはアクセスしません」ということを伝えます.

クローズするためには,BufferedReaderクラスのcloseメソッドを使います.

null

ファイルの終りに到達して,もう行が存在しない場合,readLineメソッドは null という特別な値を返します.

オブジェクト変数はオブジェクトへの参照を持っていますが,参照するオブジェクトが存在しないということを表すのに null を使います.どのようなオブジェクト変数にもnullを代入したり,nullと比較したりすることができます.

null が入っているオブジェクト変数に対してメソッドを呼び出してはいけません(オブジェクトが無いのですから).NullPointerExceptionという例外が発生します.

ファイルの中身を表示するプログラム

ファイルの中身を表示するプログラムを書いてみましょう.


このファイルをダウンロード ■ UNIX用(EUC版) ■ Windows用(SJIS版)
(上のどちらかのリンクを右ボタンでクリックして「リンク先を名前をつけて保存」して下さい)

import java.io.*;

public class PrintFile {

    // throws Exception が加わっていることに注意!!
    public static void main(String[] args) throws Exception {
	System.out.print("ファイル名は?: ");
	String filename = Keyboard.stringValue();

	// 与えられたファイル名に対応するオブジェクトを作る
	BufferedReader in = new BufferedReader(new FileReader(filename));

	while (true) {
	    // 1行読み取る
	    String s = in.readLine();
	    // ファイルの終わりに到達したら抜ける
	    if (s == null) {
		break;
	    }
	    System.out.println(s);
	}
	// ファイルをクローズする
	in.close();
    }
}


mainの後に throws Exception という記述が加わっています.これは,mainの中で例外が発生する可能性があるということを表明しています.いまのところはおまじないだと思ってください.この記述がないとコンパイルできません.

演習 (ファイルを逆順に表示)

引数で与えられたファイルの中身を,最後の行から先頭の行に向かって表示するプログラム ReverseLine を作ってください.ファイルの行数は100行以内と仮定してかまいません.

     (こんな内容で適当なファイル(ここではtestfile)を用意.)

% cat testfile
あいうえお
かきくけこ
さしすせそ

     (ファイル名を引数で指定して逆順に表示)

% java ReverseLine testfile
さしすせそ
かきくけこ
あいうえお

ヒント: 引数で指定したファイル名は args[0] に入っています.

ヒント: 逆順に出力するには,一度Stringの配列に覚えておく必要があります.Stringの配列を作るには以下のように書きます.

String[] s = new String[100];

最初の行を s[0] に,次の行を s[1] にというように覚えておき,ファイルの最後まで来たら逆順に表示すれば良いでしょう.

最後から表示するには配列のどの添字まで使ったか(つまりファイルの行数)を覚えておく必要があります.

引数を指定せずに実行したら,args[0] を参照しようとした瞬間にエラーが発生します.余裕がある人は引数が指定されているかをチェックしてください.引数があるかどうかは args.length の値をチェックすればよいです.

ファイルへの書き込み

ファイルへの書き込みも,基本的な考え方はファイルの読み込みと同じです.

FileWriterクラス

java.io.FileWriterクラスは,テキストファイルに書き込むためのクラスです.自動的にUnicodeからファイルのコード(EUCコードなど)へのコード変換も行います.

以下のようにして,指定したファイルをオープンし,書き込めるようにします.

  FileWriter fout = new FileWriter("ファイル名");

このクラスを直接使うと効率が悪いので,普通は次のPrintWriterクラスを使います.

PrintWriterクラス

java.io.PrintWriterクラスは,フォーマットされた文字列を出力するためのクラスです.このクラスも自動的にバッファリングを行います.つまり,ディスクへの書き込みをすぐには行わずにメモリにためておいて,後で一気に書くことで効率化を図っています.

  FileWriter fout = new FileWriter("ファイル名");
  PrintWriter out = new PrintWriter(fout);

  out.println("Hello, World");  // ファイルへ書き出し
  out.close();

このクラスはprintln等,たくさんのメソッドを備えています.詳しくはAPIドキュメントを参照してください.

このクラスにも,closeメソッドがあります.ファイルを書き込んだ後は,closeメソッドを呼ぶ必要があります.これを忘れると,ファイルが正常に書き込まれない場合があります.

課題7-1 (格言)

ランダムに "(名詞)とは(動詞)する(名詞)である" という文を作って,maxim.txt というファイルに書き出すプログラム Maxim を作ってください.文を10個作ったら終わるようにしてください.

犬とは食べる帽子である.
やかんとは寝るごみである.
コアラとは食べる犬である.
....(以下省略)

(名詞),(動詞)の部分はファイルから取得します.名詞や動詞の候補をそれぞれ meishi.txt, doushi.txt というファイルに書いておきます.ファイルのフォーマットは,1行に1語です.それぞれのファイルは100行以内と仮定してかまいません.

meishi.txtの例

犬
やかん
コアラ

doushi.txtの例

食べる
寝る
叩く

ヒント0: 段階的詳細化を忘れないように.

ヒント1: 最初に名詞を String[] meishi, 動詞を String[] doushi というStringの配列に覚えましょう.

ヒント2: 配列から無作為に名詞を取り出すには,名詞の個数を m とすると,乱数で 0 〜 m-1 までの整数を作って,その値を meishi の添字として使います.名詞や動詞の個数は,ファイルの行数を数えて取得してください.

余裕がある人は何かおもしろい追加機能をつけてください.

1. ソースプログラム,2. 実行結果,3. 苦労したところや改善すべき点,感想 を書くこと.