入出力API

結局java.ioパッケージもやることになってしまいました。残当


基本となる抽象クラスはこの4つです。

public abstract class InputStream
public abstract class OutputStream
public abstract class Reader
public abstract class Writer

InputStreamとOutputStreamはバイト単位の入出力を行います。Javaの型でいうとbyte(1byte)。対してReaderとWriterは文字単位の入出力を行います。Javaの型でいうとchar(2bytes)。この4つは必ずAPIドキュメント参照のこと。クラス名のつけ方からこれらのクラスを継承しているクラスは一目でわかるようになっています。

public class InputStreamReader extends Reader
public class OutputStreamWriter extends Writer

バイトストリームと文字ストリームをつなぐクラスです。ここで問題になるのは文字コードの変換です。例えばWindowsのShift-JISのテキストファイルのバイト列をJavaのchar型(UTF-16)の文字ストリームに直す、またはその逆が必要が出てくるわけです。この2つのクラスはそれを行います。コンストラクタでInputStreamやOutputStreamをとり、自身はReaderやWriterを継承します。この時コンストラクタで指定しなければデフォルトの文字セットを使います。WindowsだとShift_JISだと思います。

public class File

ファイルを表します。存在していないファイルも表せます。いわゆる「ファイル」だけでなくディレクトリも表します。考え方が多少UNIX的かもしれません。作ったり消したりディレクトリの中身を列挙したり、いろいろできます。ファイルに関わるクラスのコンストラクタで目にすることになるでしょう。

public class FileInputStream extends InputStream
public class FileOutputStream extends OutputStream

ファイル入出力の基本。バイナリデータを扱いたい場合はこのまま、文字単位の入出力を行いたいなら前述のInputStreamReader/OutputStreamWriterを使いましょう。と見せかけてそこら辺をやってしまうFileReader/FileWriterという甘えクラスもあります。

public class BufferedInputStream extends FilterInputStream
public class BufferedOutputStream extends FilterOutputStream
public class BufferedReader extends Reader
public class BufferedWriter extends Writer

バッファリング機能を追加するクラス群です。ある程度の大きさのデータを一度メモリに一気に読んでしまい、そのメモリから読み込むようにしたり、書き込み要求がきてもすぐには書かず、一度メモリに書き込んで一杯になったりしたときに実際に書き込んだりするようにします。特に出力において強制的に書き込ませてバッファを空にするにはflush()メソッドを使います。当然ながらclose()したときにバッファはフラッシュされますが、ネットワークへの書き込み時などに問題になる場合があります。
BufferedReaderはString readLine()という1行読み込んで改行文字を除いた文字列を返すメソッドを提供します。ついでにBufferedWriterはnewLine()という改行を書き込むメソッドを提供します。readLine()はJava1.4までの行単位の入力の典型でした。


System.inについて

public static final InputStream in;

標準入力System.inはInputStream型となっています。今までのクラスを使って入力をそのままエコーするコードを書いてみましょう。

// for 1.4
BufferedReader in = new BufferedReader(
    new InputStreamReader(System.in));
String line;
while ((line = in.readLine()) != null){
  System.out.println(line);
}
in.close();

readLine()はEOFに到達するとnullを返します。また、改行は削除して返します。System.inはInputStreamですから、この部分をnew FileInputStream("ファイル名")に変えればファイルの内容をそのまま表示するプログラムのできあがりです。BufferedReaderは例外を投げるので対処コードを書かないとコンパイルエラーになったりとかよく分からないクラスが急にたくさん出てきたりとかでJavaのキーボード入力は初心者にお手軽ではないものの、順を追って見ていけばそれ以上のことも含めて整備されていることが分かるはず。


System.out/System.errについて

public static final PrintStream out;
public static final PrintStream err;

いつもprintln()して遊んでいると思いますが、あれらがあるのがPrintStreamクラスです。OutputStreamの子孫です。intやObjectやStringなんかを引数にしてストリームに書き込みます。PrintWriterというクラスもあります。なんでPrintStreamなんてものがあるの?と思うなら、Reader/WriterクラスはJava1.1で追加されたこと、Java1.0でもSystem.out.println("Hello!");していたこと、からお察しください*1。なお、これらのクラスはコンストラクタ以外では例外を飲み込みます。一応checkError()メソッドで確認はできますが、HelloWorldで例外処理を書かないとエラーとかだるいので許してあげてください。しかしファイルへprintlnしたら途中で例外が発生しても飲み込まれるというのはちょっと怪しいですよね。きっとJava最初期のゴタゴタと後の設計方針と互換性の問題で、ダークサイドなんで気にしないでください。
答え:いろんなものにprintlnしたいならPrintWriterを使おう


スライドの補足

catch (FileNotFoundException e) {
  // ...
}
catch (IOException e) {
  // ...
}

FileNotFoundExceptionはIOExceptionを継承しています。なので実はエラーをなくしたいだけならばFileNotFoundExceptionの節は要りません。ちなみにこの場合、FileNotFoundExceptionが投げられた場合上の節のみが実行され、IOExceptionの節は実行されません。上から順番に見ていき、初めにマッチ(instanceof的な)したものが1つだけ実行されるという感じでしょうか。

*1:私がJavaを始めたのは中2の時ですでに1.4だったけど…。