【Java I/O】 混乱しそうな入出力(I/O)ストリームクラスはまずはここから

同じような名前のクラスがたくさんあって投げ出したくなるのがJavaの入出力(I/O)ストリームクラス...
混乱せずに整理するためのポイントをまとめてみます.

Input系かOutput系か?

これは名前の通りなのでイメージしやすいですね.

超絶わかりにくくしている大混乱(大げさ)をときほぐすには,次の2つを意識するといいでしょう.

Reader/WriterかStreamか?

「ReaderとInputStreamって何が違うの?どっちも入力でしょ?」ということですが,クラス名に

  • Reader/Writerとあれば→char型 or String型専用
  • InputStream/OutputStreamとあれば→何型でもOKな汎用ストリーム

です.

ちなみに,InputStream, OutputStream, Reader, Writerの各クラスは抽象クラスになっていて,ここからいろんなクラスが派生されているのですが,たいていの派生クラスではクラス名に元クラスの名前が含まれているので,上に書いたパターンで見分けていくことができます.

試しにFileReaderのAPIを確認してみると,

FileReaderは、文字のストリームを読み込むために使用されます。rawバイトのストリームを読み込むときは、FileInputStreamを使用してください。 https://docs.oracle.com/javase/jp/8/docs/api/java/io/FileReader.html

となっています.

コンストラクタの引数に注目

もうひとつ.引数にどんな型をとるかによって2分されます.

  • (1) 引数がリソース: 直接リソースにアクセスできます.
  • (2) 引数がStreamクラス: どこかで(1)のストリームクラスをラップすることになります.

なんで(2)みたいなのを使うのかというと,(1)のクラスでは"原始的な"メソッドしか提供されておらず扱いにくかったり,性能面で問題があるからです.イメージとしては,(1)がリソースとの直接的な界面になっていて,そこにオプション機能である"上級"パイプをつなげていく...ような感じです(※あくまで私のイメージです...)

APIで確認してみます.
まずはFileInputStream.このクラスのコンストラクタは

FileInputStream(File file)
FileInputStream(FileDescriptor fdObj)
FileInputStream(String name)

の3つが定義されています.FileDescriptorは,なんとなくファイルへのアクセスに関する情報っぽいし,明らかにストリームクラスではありません.
なので(1)になります.

続いて,ObjectInputStream.

ObjectInputStream(InputStream in)

はい,ばっちり(2)のパターンですね.
ここで型がInputStreamとなっていますが,これは先ほど確認した抽象クラスでした. なので,例えば,次のようなチェーンというか入れ子のコンストラクタを書くことができます.

ObjectInputStream is = new ObjectInputStream(
                                             new BufferedInputStream(new FileInputStream(filename)))

BufferedInputStreamも(2)のパターンです. そして,BufferedInputStreamの概要を読んでみると

BufferedInputStreamは、ほかの入力ストリームに機能、特に入力をバッファに格納する機能とmarkおよびresetメソッドをサポートする機能を追加します。 BufferedInputStream (Java Platform SE 8)

とあり,高度な機能を補完する役割を担っていることが伺えます.

これを頭に入れておくとだいぶすっきりします.