Java超|入出力とストリームのしくみ

データは、川のように流れてプログラムへ入り、また外へ出ていく。ストリームの考え方をつかむと、画面表示・キーボード入力・ファイル入出力が、ひとつの大きな流れとして理解できるようになります。

Javaのプログラムでは、画面に文字を表示したり、キーボードから入力を受け取ったりする処理がよく出てきます。

たとえば、次のような処理です。

System.out.println("メッセージ");

これは、画面に文字を表示する出力です。

一方、キーボードから文字を受け取る処理では、System.in や BufferedReader などが登場します。

最初は、画面表示、キーボード入力、ファイル操作は、それぞれ別々のしくみに見えるかもしれません。

しかし、Javaではこれらを ストリーム という共通の考え方で整理できます。

ストリームとは、簡単にいうと データの流れ です。

ドラゴンボール風にたとえると、戦士本部と各地の戦士たちをつなぐ通信ラインのようなものです。

悟空やベジータから本部へ修行報告が届く流れもあれば、本部から戦士たちへ任務指令が送られる流れもあります。

Javaの考え方ドラゴンボール風のイメージ
入力外から戦士本部へ届く報告
出力戦士本部から外へ出す指令
ストリーム報告や指令が流れる通信ライン
System.inキーボードから届く報告の入口
System.out画面へ指令を出す出口

この記事では、入出力とは何か、ストリームとは何か、System.in と System.out は何をしているのか、そして Sample6.java のプログラムでキーボード入力がどのように文字列として読み取られるのかを整理していきます。

入出力とは何か

入出力とは、プログラムと外部とのやりとりです。

プログラムの中だけで計算して終わる処理もありますが、実際のプログラムでは外部とのやりとりがとても重要です。

たとえば、ユーザーが入力した文字を受け取る、計算結果を画面に表示する、データをファイルへ保存する、といった処理があります。

種類内容
入力外からデータを受け取ることキーボードから文字を受け取る
出力データを外へ出すこと画面にメッセージを表示する

ドラゴンボール風に言えば、入力は戦士から本部へ届く報告です。

「修行が完了しました」
「重力設定を変更してください」
「次の任務地を教えてください」

このような情報が外から本部へ入ってくるイメージです。

一方、出力は本部から戦士へ出す指令です。

「修行メッセージを入力してください」
「修行記録を確認しました」
「次の任務へ向かってください」

このように、プログラムの内側から外へ情報を出すのが出力です。

ストリームとは何か

ストリームは、データの流れを表す考え方です。

Javaでは、キーボード、画面、ファイルなど、相手が違っても、データが流れる通り道として入出力を考えます。

つまり、ストリームは、装置そのものではなく、データが流れる道に注目する考え方です。

相手データの流れ
キーボード外からプログラムへ文字が入る
画面プログラムから外へ文字が出る
ファイルファイルから読む、またはファイルへ書く

ドラゴンボール風にたとえると、ストリームは戦士本部の通信ラインです。

報告が本部へ向かう道もあれば、本部から戦士へ指令が向かう道もあります。

相手が悟空でも、ベジータでも、ブルマの端末でも、ファイル保管庫でも、情報が流れるという考え方は共通です。

Javaでも同じです。

画面、キーボード、ファイルは別々のものですが、データが入る・出るという流れで見ると、共通した考え方で扱えるようになります。

なぜストリームで考えるのか

もし、キーボード用、画面用、ファイル用で、完全に別々の考え方を覚える必要があったら、入出力の学習はかなり大変になります。

しかし、ストリームという考え方があると、次のように整理できます。

やりたいことストリームでの見方
キーボードから読む入力の流れを読む
画面に表示する出力の流れへ書く
ファイルから読むファイルからの入力の流れを読む
ファイルへ保存するファイルへの出力の流れへ書く

つまり、相手が変わっても、基本は「読む」「書く」という流れで考えられます。

ドラゴンボール風に言えば、通信の相手が戦士本部でも修行場でも任務地でも、情報が流れるという考え方は変わりません。

通信ラインを理解すれば、どこから情報が来て、どこへ情報を送るのかが見えやすくなります。

図:入出力とストリームの全体像

この図が示していること

この図では、Javaプログラムを中心に、キーボード、画面、ファイルとの間でデータが流れる様子を表しています。

キーボードからプログラムへ向かう流れは入力です。
画面へ向かう流れは出力です。
ファイルとは、読み込みと書き込みの両方ができます。

図の要素意味
System.inキーボードからの入力の入口
System.out画面への出力の出口
入力ストリーム外からプログラムへ入るデータの流れ
出力ストリームプログラムから外へ出るデータの流れ
ファイル読むことも書くこともできる入出力先

この図から分かることは、入出力は相手が違っても、データの流れとして共通して考えられるということです。

画面とキーボードでもストリームが使われている

ストリームというと、ファイルの読み書きだけを思い浮かべるかもしれません。

しかし、すでに使ってきた画面表示やキーボード入力にも、ストリームの考え方が関係しています。

画面への出力では、よく次のように書きます。

System.out.println("修行開始");

ここで使っている System.out は、標準出力を表します。

標準出力とは、プログラムから外へデータを出す基本の出口です。
多くの場合、画面に表示されます。

一方、キーボードからの入力では System.in が使われます。

System.in

System.in は、標準入力を表します。

標準入力とは、外からプログラムへデータが入ってくる基本の入口です。
多くの場合、キーボードからの入力です。

名前意味主な相手
System.in標準入力キーボード
System.out標準出力画面

ドラゴンボール風にたとえると、System.in は戦士から本部へ届く報告窓口です。

System.out は、本部から戦士や画面へ指令を出す連絡窓口です。

標準入力と標準出力

標準入力と標準出力は、Javaプログラムが外部とやりとりするための基本的な入口と出口です。

項目役割ドラゴンボール風のイメージ
標準入力外からデータを受け取る戦士から本部へ届く報告
標準出力外へデータを出す本部から出す指令や連絡
System.in標準入力を表す報告を受け取る窓口
System.out標準出力を表す指令を出す窓口

これまで何気なく使ってきた System.out.println も、実はストリームを使った出力の一種です。

System.out.println("修行メッセージを入力してください。");

この1行は、プログラムの中にある文字列を、System.out という出力の流れに乗せて画面へ送っています。

一方、System.in は、キーボードから入ってくるデータの流れです。

ただし、System.in だけでは、文字列を1行ずつ扱うには少し不便です。

そこで、InputStreamReader や BufferedReader というクラスを組み合わせて、読み取りやすい形に整えていきます。

キーボードからの入力と出力までの流れを見る

ここでは、キーボードから修行メッセージを入力し、それを画面に表示するプログラムを見ていきます。

ファイル名:Sample6.java

import java.io.*;

class Sample6
{
    public static void main(String[] args)
    {
        System.out.println("修行メッセージを入力してください。");

        try
        {
            InputStreamReader isr =
                new InputStreamReader(System.in);

            BufferedReader br =
                new BufferedReader(isr);

            String str = br.readLine();

            System.out.println("入力された修行メッセージは「" + str + "」です。");
        }
        catch(IOException e)
        {
            System.out.println("入出力で問題が発生しました。");
        }
    }
}

このプログラムでは、最初に画面へ入力を促すメッセージを表示します。

System.out.println("修行メッセージを入力してください。");

そのあと、try ブロックの中でキーボード入力を読み取る準備をしています。

InputStreamReader isr =
    new InputStreamReader(System.in);

BufferedReader br =
    new BufferedReader(isr);

String str = br.readLine();

そして、読み取った文字列を画面へ表示します。

System.out.println("入力された修行メッセージは「" + str + "」です。");

プログラムの流れ

Sample6.java の処理は、次の順番で進みます。

手順内容
1画面に入力を促すメッセージを表示する
2System.in をもとに InputStreamReader を作る
3InputStreamReader をもとに BufferedReader を作る
4readLine で1行読み込む
5読み込んだ文字列を画面に表示する
6入出力で問題が起きたら IOException を catch する

この流れを見ると、キーボードから入力された文字が、いきなり String として読めているわけではないことが分かります。

System.in から入ってきたデータを、InputStreamReader で文字として扱いやすくし、BufferedReader で1行ずつ読みやすくしているのです。

ドラゴンボール風にたとえると、戦士から届いた通信データを、まず読める文字に変換し、さらにブルマの記録システムが1通の報告として整理しているようなイメージです。

InputStreamReader の役割

InputStreamReader は、入力ストリームを文字として扱いやすくするためのクラスです。

Sample6.java では、次のように使っています。

InputStreamReader isr =
    new InputStreamReader(System.in);

System.in は、キーボードから入ってくる標準入力です。

ただし、System.in はそのままだと、文字列を1行ずつ読むためには扱いにくい形です。

そこで、InputStreamReader を使い、入力の流れを文字として扱いやすくします。

クラス役割
System.inキーボードから入ってくる標準入力
InputStreamReader入力ストリームを文字として扱いやすくする

ドラゴンボール風にたとえると、System.in は戦士から届く生の通信データです。

しかし、そのままでは記録係が読みやすい報告書にはなっていません。

InputStreamReader は、その生の通信データを、文字として読める形に変換する通訳係のような役割を持っています。

BufferedReader の役割

次に登場するのが BufferedReader です。

BufferedReader br =
    new BufferedReader(isr);

BufferedReader は、文字を効率よく読み込むためのクラスです。

さらに、readLine メソッドを使って、1行単位で文字列を読み取れるようにしてくれます。

クラス・メソッド役割
BufferedReaderバッファを使って効率よく文字を読み込み、1行ずつ扱いやすくする
readLine1行分の文字列を読み取る

バッファとは、データをいったんためておく場所のようなものです。

細かい文字をその場その場で少しずつ処理するのではなく、ある程度まとめて扱えるようにするため、効率よく読み取れます。

ドラゴンボール風にたとえると、通信の断片をその場で一つずつ読むのではなく、ブルマの記録装置がいったんまとめてから、1つの報告として読み上げるようなものです。

readLine で1行読み込む

実際にキーボードから1行読み込んでいるのは、次の行です。

String str = br.readLine();

readLine は、1行分の文字列を読み取るメソッドです。

ユーザーがキーボードで文字を入力し、Enter を押すと、その1行が文字列として読み取られます。

たとえば、ユーザーが次のように入力したとします。

重力修行を開始します

すると、その1行全体が str に入ります。

入力str に入る内容
重力修行を開始します重力修行を開始します
かめはめ波の型を確認しますかめはめ波の型を確認します
任務報告を送ります任務報告を送ります

そのあと、次の文で画面へ表示します。

System.out.println("入力された修行メッセージは「" + str + "」です。");

つまり、キーボードから入った文字列を、プログラムの中で str という変数に入れ、System.out によって画面へ出力しているわけです。

図:キーボード入力が文字列になるまで

この図が示していること

この図では、キーボードから入力されたデータが、どのように文字列として取り出されるのかを表しています。

キーボードから入ったデータは、まず System.in という標準入力の入口を通ります。

その後、InputStreamReader によって文字として扱いやすくされます。

さらに BufferedReader によって効率よく読み込めるようになり、readLine によって1行分の文字列として取得されます。

流れ役割
キーボードユーザーが文字を入力する
System.in標準入力の入口
InputStreamReader入力を文字として扱いやすくする
BufferedReader効率よく読み込み、1行ずつ扱えるようにする
readLine1行分の文字列を取得する
System.out.println取得した文字列を画面へ表示する

この図から分かることは、キーボード入力は複数のクラスを組み合わせて、読みやすい形に整えられているということです。

実行結果のイメージ

Sample6.java を実行すると、まず入力を促すメッセージが表示されます。

修行メッセージを入力してください。

ここで、たとえば次のように入力します。

重力修行を開始します

すると、次のように表示されます。

修行メッセージを入力してください。
重力修行を開始します
入力された修行メッセージは「重力修行を開始します」です。

この流れでは、入力と出力の両方が使われています。

表示・入力種類使っているもの
修行メッセージを入力してください。出力System.out
重力修行を開始します入力System.in
入力された修行メッセージは...出力System.out

ここで重要なのは、キーボードから入ったデータがプログラム内の str に入り、その str の内容を画面へ出力していることです。

つまり、データは次のように流れています。

キーボード → System.in → InputStreamReader → BufferedReader → readLine → String str → System.out → 画面

ドラゴンボール風にたとえると、戦士が出した修行報告が、本部の入口、通訳係、記録係を通って巻物に記録され、最後に画面へ掲示されるような流れです。

なぜ import java.io.*; が必要なのか

Sample6.java の先頭には、次の1行があります。

import java.io.*;

java.io は、入出力に関するクラスがまとめられているパッケージです。

今回のプログラムでは、InputStreamReader、BufferedReader、IOException を使っています。

これらは java.io パッケージに含まれるクラスです。

クラス役割
InputStreamReader入力を文字として扱いやすくする
BufferedReader効率よく文字を読み込む
IOException入出力で問題が起きたときの例外

import java.io.*; と書くことで、java.io パッケージ内のクラスを使いやすくしています。

もし import を書かない場合、クラス名を使うときに java.io.BufferedReader のように、パッケージ名まで含めて書く必要があります。

書き方特徴
import java.io.*; を書くBufferedReader のように短く書ける
import を書かないjava.io.BufferedReader のように長く書く必要がある

ドラゴンボール風に言えば、java.io は入出力に関する巻物棚です。

最初に import java.io.*; と書くことで、入出力の巻物棚を使う準備をしているイメージです。

なぜ IOException に備えるのか

Sample6.java では、try-catch が使われています。

try
{
    InputStreamReader isr =
        new InputStreamReader(System.in);

    BufferedReader br =
        new BufferedReader(isr);

    String str = br.readLine();

    System.out.println("入力された修行メッセージは「" + str + "」です。");
}
catch(IOException e)
{
    System.out.println("入出力で問題が発生しました。");
}

ここで catch しているのは IOException です。

IOException は、入出力処理で問題が起きたときに使われる例外です。

キーボード入力やストリームの読み込みは、プログラムの内部だけで完結する計算とは違います。
外部とのやりとりが関係します。

外部とのやりとりでは、想定外の問題が起きる可能性があります。

処理問題が起きる可能性
キーボード入力入力の読み込みに失敗する可能性
ファイル読み込みファイルが存在しない、読み取れない可能性
通信相手と接続できない可能性
ストリーム処理データの流れが途中で問題を起こす可能性

そのため、入出力処理では IOException に備えることがあります。

ドラゴンボール風にたとえると、外部との通信には不確実さがあります。

通信データが途中で途切れるかもしれません。
通信装置が一時的に反応しないかもしれません。
報告が途中で壊れて届くかもしれません。

そのような入出力の問題に備えるために、IOException を catch しています。

try-catch の役割

このプログラムの try ブロックには、入出力に関係する処理が入っています。

String str = br.readLine();

readLine は、1行分の文字列を読み取るメソッドです。

このような入出力処理では、IOException が起きる可能性があります。

そのため、try の中に入れて、問題が起きた場合は catch で受け止めます。

ブロック役割
try入出力処理を実行する
catch(IOException e)入出力の問題を受け止める

try-catch を使うことで、問題が起きたときに、Javaの難しい例外メッセージだけで終わらせず、利用者に分かりやすいメッセージを表示できます。

System.out.println("入出力で問題が発生しました。");

ドラゴンボール風に言えば、通信がうまく届かなかったときに、ブルマの支援システムが状況を受け止めて、

「入出力で問題が発生しました。」

と本部に知らせるようなものです。

図:入出力処理とIOExceptionの関係

この図が示していること

この図では、入出力処理と IOException の関係を表しています。

try ブロックでは、InputStreamReader、BufferedReader、readLine を使って入力を読み取っています。

しかし、入出力は外部とのやりとりなので、問題が起きる可能性があります。

その問題に備えるために、catch(IOException e) を用意しています。

図の要素意味
try ブロック入出力処理を実行する場所
br.readLine()1行分の文字列を読み取る処理
IOException入出力で問題が起きたときの例外
catch(IOException e)入出力の問題を受け止める場所

この図から分かることは、入出力処理では外部とのやりとりがあるため、IOException に備える必要があるということです。

画面・キーボード・ファイルを同じ考え方で見る

この学習で大切なのは、Sample6.java のコードを覚えることだけではありません。

もっと大切なのは、入出力をストリームという共通の流れとして見ることです。

この考え方が身につくと、今後ファイル入出力を学ぶときにも理解しやすくなります。

今回の相手今後出てくる相手共通点
キーボードファイルデータを読む
画面ファイルデータを書く
System.inファイル入力用ストリーム入力の流れ
System.outファイル出力用ストリーム出力の流れ

キーボード入力では、System.in からデータを受け取りました。

ファイル入力でも、考え方としてはファイルからデータを受け取ります。

画面出力では、System.out へデータを流しました。

ファイル出力でも、考え方としてはファイルへデータを流します。

ドラゴンボール風に言えば、通信の相手がキーボードなのか、画面なのか、ファイルなのかが違うだけです。

情報が流れるという基本は変わりません。

ストリームを理解すると入出力がつながって見える

ストリームの理解が深まると、Javaの入出力はばらばらの知識ではなく、ひとつながりの考え方として見えてきます。

用語内容
ストリームデータの流れ
入力ストリーム外からプログラムへ入る流れ
出力ストリームプログラムから外へ出る流れ
System.in標準入力
System.out標準出力
InputStreamReader入力を文字として扱いやすくする
BufferedReader効率よく読み込み、1行ずつ扱いやすくする
readLine1行の文字列を読み込む
IOException入出力で起こる問題に備える例外

最初は InputStreamReader や BufferedReader の名前が長く感じるかもしれません。

しかし、役割で分けて考えると分かりやすくなります。

クラス・メソッドドラゴンボール風の役割
System.in報告が入ってくる本部の入口
InputStreamReader生の通信を文字として読める形にする通訳係
BufferedReader通信をためて1行ずつ読みやすくする記録係
readLine1通の報告として読み取る
System.out画面へ指令を出す出口

この流れが分かると、Sample6.java のコードもかなり読みやすくなります。

キーボードから入った文字が、System.in から入り、InputStreamReader と BufferedReader を通って、readLine で String として取り出される。

そして、その String を System.out.println で画面へ表示する。

この一連の流れこそが、ストリームによる入出力の基本です。