Java超|14章のまとめ

予想外の異常に備え、外の世界とデータをやり取りできるようになる。14章は、Javaプログラムを「実際に使える道具」へ近づけるための大切な章です。

14章では、Javaプログラムをより実用的にするための大切な力を学びました。

大きな柱は2つです。

14章の柱学んだこと
例外処理実行中に起こる予想外の問題へ対応する
入出力処理画面、キーボード、ファイル、実行時の引数とデータをやり取りする

これまでの章では、変数、条件分岐、繰り返し、配列、クラスなど、Javaプログラムの基本となる道具を学んできました。

しかし、実際に使えるプログラムを作るには、それだけでは足りません。

プログラムは、いつも理想どおりに動くとは限りません。

配列の範囲をこえてしまうこともあります。
数値に変換できない文字が入ってくることもあります。
指定したファイルが見つからないこともあります。

また、実用的なプログラムでは、外部とのやり取りも欠かせません。

キーボードから入力を受け取る。
画面へ結果を表示する。
ファイルへ保存する。
ファイルから読み込む。
実行時にファイル名を指定する。

このような処理ができるようになると、プログラムはぐっと現実的になります。

ドラゴンボール風にたとえると、14章は、戦士がただ強い技を出すだけでなく、任務中の異常に備え、戦士本部と通信し、修行記録をファイルに残し、次の任務へ活かせるようになる章です。

強い技を持っていても、想定外の事態に対応できなければ実戦では困ります。

また、任務の結果や修行記録を残せなければ、次の判断につなげることも難しくなります。

14章では、Javaプログラムを「ただ動くもの」から「異常に備え、外部とつながり、データを活用できるもの」へ進める考え方を学びました。

例外処理は予想外の出来事に備えるしくみ

Javaのプログラムは、常に予定どおりに動くとは限りません。

たとえば、次のような問題が起こることがあります。

起こる問題Javaでの例
存在しない配列番号を使うArrayIndexOutOfBoundsException
数字に変換できない文字を変換するNumberFormatException
ファイルが見つからないIOException など
null の参照先を使うNullPointerException

このような実行中の問題を知らせるしくみが 例外 です。

例外は、単なる失敗ではありません。

プログラムが「このままでは通常の処理を続けられません」と判断したときに発生する、異常を知らせる合図です。

ドラゴンボール風に言えば、任務中に危険を知らせる警告札が飛ぶようなものです。

たとえば、修行名簿に5人分の欄しかないのに、10番目の欄へ記録しようとしたら、戦士本部の記録システムが「その欄は存在しません」と警告を出します。

Javaでも同じです。

配列の範囲外へアクセスすると、Javaは例外を発生させます。

ドラゴンボール風の場面Javaの考え方
存在しない名簿欄へ記録しようとする配列の範囲外アクセス
数字ではない札を戦闘力として読む数値変換の失敗
指定された記録ファイルが本部にないファイルが見つからない
警告札が飛ぶ例外が発生する

例外処理を学ぶことで、プログラムは「問題が起きたら突然終わるもの」ではなく、「問題が起きたときに受け止めて対応できるもの」になります。

try、catch、finally の役割

例外処理の基本として学んだのが、try、catch、finally です。

それぞれの役割ははっきり分かれています。

ブロック役割ドラゴンボール風のイメージ
try例外が起こるかもしれない処理を書く危険を含む任務を実行する場所
catch例外が起きたときに受け止める支援役が警告札を受け取る場所
finally例外の有無に関係なく最後に行う処理を書く任務後に装置や記録ファイルを片づける係

try は、まず処理を実行する場所です。

たとえば、ファイルを開く、配列へ値を入れる、文字列を数値へ変換する、といった処理を書きます。

catch は、try の中で例外が起きたときに動きます。

例外の種類に合う catch があれば、そこで例外を受け止めて処理できます。

finally は、正常に処理が終わっても、例外が起きても、最後に必ず行いたい処理を書く場所です。

ファイルを閉じる、接続を終了する、記録装置を止める、といった後片付けに向いています。

try-catch-finally の流れ

try-catch-finally の流れを整理すると、次のようになります。

状況処理の流れ
例外が起きないtry が最後まで実行され、finally が実行される
例外が起きて catch が合うtry の途中で中断され、catch が実行され、finally が実行される
finally があるどちらの場合でも最後に実行される

ここで大切なのは、try の中で例外が発生すると、その後ろの try 内の通常処理は実行されないという点です。

たとえば、修行記録を書き込む途中で、存在しない欄を指定してしまったとします。

その時点で try の通常処理は止まり、catch に移ります。

成功メッセージが try の後ろに書いてあっても、例外発生後なら実行されません。

ドラゴンボール風に言えば、任務中に危険な警告札が飛んだら、そのまま成功報告へ進むのではなく、まず支援役が状況を受け止めるということです。

図:例外処理の全体像

この図が示していること

この図は、14章前半で学んだ例外処理の流れを表しています。

try は、例外が起こるかもしれない処理を実行する場所です。

その途中で問題が起きると、例外が発生し、対応する catch が受け止めます。

そして、finally がある場合は、例外が起きても起きなくても最後に実行されます。

図の要素意味
tryまず処理を実行する
例外発生通常処理を続けられない問題が起きる
catch例外を受け止める
finally最後の後片付けをする

この図から分かることは、例外処理は「エラーを消すためのもの」ではなく、異常が起きたときの流れを安全に整理するしくみだということです。

例外はクラスのオブジェクトである

14章では、例外の正体がクラスのオブジェクトであることも学びました。

例外は、ただの文字列メッセージではありません。

Javaでは、Throwable を土台にしたクラス階層の中で、例外やエラーが整理されています。

大まかには、次のような構造です。

Throwable
├─ Error
└─ Exception
   ├─ IOException
   └─ RuntimeException
      └─ ArrayIndexOutOfBoundsException

このように、例外はクラスの親子関係で整理されています。

たとえば、配列の範囲外アクセスで発生する ArrayIndexOutOfBoundsException も、例外クラスのひとつです。

catch では、その例外オブジェクトを変数で受け取れます。

catch(ArrayIndexOutOfBoundsException e)
{
    System.out.println(e + " が発生しました。");
}

ここでの e は、発生した例外オブジェクトを指す変数です。

つまり、catch の丸かっこは、次のような意味を持っています。

部分意味
ArrayIndexOutOfBoundsException受け取りたい例外の種類
e受け取った例外オブジェクトを指す変数

ドラゴンボール風にたとえると、任務中に飛んできた警告札そのものを、支援役が受け取っているようなものです。

警告札には、どんな異常なのか、どんな原因なのか、どこで起きたのかを調べる手がかりが含まれています。

Error と Exception の違い

Throwable の下には、大きく Error と Exception があります。

分類意味基本的な考え方
Error深刻な問題通常の例外処理の中心にはしない
Exceptionプログラムで対応を考える例外try-catch や throws の対象になりやすい

Error は、実行環境やシステム全体に関わる深刻な問題を表すことがあります。

ドラゴンボール風に言えば、修行名簿の記入ミスではなく、戦士本部の訓練場そのものが崩れ始めるような状態です。

一方、Exception は、プログラム側で対応を考える対象です。

ファイルが見つからない、入出力に失敗する、独自に定義した異常が発生する、というような場面で扱います。

独自例外を作れる意味

Javaでは、用意されている例外だけでなく、自分で独自の例外クラスを作ることもできます。

学習では、Exception を継承する形が基本でした。

class TrainingException extends Exception
{
}

このように書くと、TrainingException という独自の例外クラスを作れます。

独自例外を作る意味は、異常に名前を付けられることです。

例外名伝わる意味
Exception何らかの例外
TrainingException修行装置に関する異常
IOException入出力に関する異常
ArrayIndexOutOfBoundsException配列の範囲外アクセス

ドラゴンボール風に言えば、ただ「異常札」と呼ぶのではなく、「修行装置警告札」「記録ファイルエラー」「名簿範囲外警告札」のように、警告の種類を分けることです。

これにより、クラスを使う人は、どんな異常が起こる可能性があるのかを理解しやすくなります。

throw と throws の意味

14章では、例外を受け取るだけでなく、自分で送出する方法も学びました。

ここで大切なのが throw と throws の違いです。

書き方意味ドラゴンボール風のイメージ
throw例外オブジェクトを実際に送出する警告札をその場で投げる
throwsメソッドが例外を送出する可能性を宣言する任務入口に注意書きを出す

throw は、実際に例外を投げる処理です。

throw new TrainingException();

これは、TrainingException オブジェクトを作り、その場で送出するという意味です。

一方、throws はメソッド宣言に書きます。

public void setTraining(int level, double gravity) throws TrainingException

これは、このメソッドでは TrainingException が送出される可能性があります、と呼び出し側へ知らせる宣言です。

throws 自体が例外を発生させるわけではありません。

ドラゴンボール風に言えば、throw は実際に警告札を投げることです。
throws は「この任務では警告札が飛ぶ可能性があります」と入口に書いておくことです。

例外を処理するか、上へまかせるか

例外が送出される可能性のあるメソッドを使うとき、呼び出し側には主に2つの選択肢があります。

選択肢内容
try-catch で処理するその場で例外を受け止める
throws で上へ渡す自分では処理せず、呼び出し元へまかせる

これは、クラス設計と利用の両方に関わる大切な考え方です。

クラスを作る側は、異常な状態を見つけたら throw で知らせます。

そして、そのメソッドに throws を付けて、利用者へ例外の可能性を伝えます。

クラスを使う側は、その例外を自分で catch するのか、さらに上へ渡すのかを決めます。

ドラゴンボール風に言えば、現場の支援役がその場で対応するのか、戦士本部の上位担当へ判断を送るのかを選ぶようなものです。

明示的な例外処理が不要な例外もある

14章では、すべての例外に同じように try-catch や throws が必要なわけではないことも学びました。

特に、Error のサブクラスや RuntimeException のサブクラスは、明示的な例外処理を必ずしも要求しません。

種類明示的な try-catch / throws
Error 系通常は必須ではない
RuntimeException 系必須ではない
RuntimeException 以外の Exception 系必要になることが多い

RuntimeException 系には、ArrayIndexOutOfBoundsException や NullPointerException などがあります。

これらは、基本的にはコードの使い方を見直して防ぐべきものが多いです。

たとえば、配列の範囲外アクセスは、catch で受け止めるよりも、添字が正しい範囲にあるかを確認することが大切です。

ドラゴンボール風に言えば、名簿に5人分しか欄がないのに10番目へ記録しようとするなら、警告札への対応以前に、まず欄番号を正しく確認するべきということです。

入出力はストリームで考える

14章のもうひとつの大きな柱が、入出力処理です。

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

処理内容
入力外からプログラムへデータを受け取る
出力プログラムから外へデータを出す

Javaでは、この入出力を ストリーム という考え方で整理します。

ストリームとは、データの流れです。

相手ストリームでの見方
キーボード外からプログラムへデータが入る
画面プログラムから外へデータが出る
ファイル読み込みも書き込みもできる
コマンドライン引数実行時に外から値が渡される

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

戦士から本部へ報告が届く流れもあれば、本部から戦士へ指令が送られる流れもあります。

ファイルは、戦士本部の記録室にある記録データです。

プログラムは、その記録へ書き込んだり、記録から過去の内容を読み出したりできます。

画面・キーボード・ファイルの入出力

14章では、画面、キーボード、ファイルとのデータのやり取りを学びました。

対象使ったもの役割
画面System.out標準出力
キーボードSystem.in標準入力
キーボード入力InputStreamReader、BufferedReader文字列として読み込む
ファイル出力FileWriter、BufferedWriter、PrintWriterファイルへ書き込む
ファイル入力FileReader、BufferedReaderファイルから読み込む

画面へ表示するときは、System.out.println を使いました。

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

キーボードから入力するときは、System.in を InputStreamReader で文字として扱いやすくし、BufferedReader で1行ずつ読み取れるようにしました。

BufferedReader br =
    new BufferedReader(new InputStreamReader(System.in));

ファイルへ書き込むときは、FileWriter、BufferedWriter、PrintWriter を組み合わせました。

PrintWriter pw = new PrintWriter(
    new BufferedWriter(
        new FileWriter("training.txt")
    )
);

ファイルから読み込むときは、FileReader と BufferedReader を組み合わせました。

BufferedReader br =
    new BufferedReader(new FileReader("training.txt"));

このように、相手が変わっても、データの流れとして考える点は共通しています。

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

この図が示していること

この図では、14章で学んだ入出力処理を、Javaプログラムを中心としたデータの流れとして整理しています。

キーボードからは System.in を通してデータが入ります。

画面へは System.out を通してデータが出ます。

ファイルからは FileReader と BufferedReader を通してデータを読み込みます。

ファイルへは FileWriter、BufferedWriter、PrintWriter を通してデータを書き込みます。

図の要素意味
System.in標準入力
System.out標準出力
FileReaderファイルから文字を読む
FileWriterファイルへ文字を書く
BufferedReader効率よく読み込む
BufferedWriter効率よく書き込む
PrintWriterprintln で書き込みやすくする

この図から分かることは、入出力の相手が違っても、ストリームという共通の考え方で整理できるということです。

close の大切さ

ファイルを扱うときに大切なのが close です。

ファイルは、開いて読み書きしたら終わりではありません。

使い終わったら閉じる必要があります。

br.close();

または、

pw.close();

close には、次のような意味があります。

close の役割内容
ファイルを閉じる使い終わった外部資源を解放する
書き込みを完了する保存処理をきちんと締める
後片付けをする開きっぱなしを防ぐ

ドラゴンボール風に言えば、記録室からファイルを取り出して読んだあと、きちんと閉じて棚へ戻すようなものです。

記録ファイルを開きっぱなしにしたまま任務を終えるのはよくありません。

Javaでも、ファイルを扱ったら、最後に閉じることが大切です。

この考え方は finally ともつながります。

例外が起きても起きなくても、最後に必ず行いたい後処理があるなら、finally に書くのが自然です。

ファイルを使うと実用性が広がる

ファイル入出力を学ぶと、Javaプログラムは一気に実用的になります。

画面に表示するだけでは、情報はその場で流れてしまいます。

しかし、ファイルに保存すれば、あとから読み返せます。

さらに、ファイルから読み込めば、過去の記録や大量のデータを処理に使えます。

ファイルでできること
データを保存する修行記録を training.txt に残す
データを読み込む過去の修行記録を確認する
大量データを扱う複数の戦士の戦闘力を読み込む
計算に使う最大値や最小値を求める
設定を変えやすくするソースコードではなくファイルを変更する

ドラゴンボール風に言えば、任務の記録や修行結果を戦士本部の記録ファイルに残し、次の任務前に読み返せるようになるということです。

その場の会話だけではなく、記録として未来へつなげられるようになります。

コマンドライン引数で外から値を受け取る

14章の最後では、コマンドライン引数も学びました。

コマンドライン引数とは、プログラムを実行するときに外から渡す値のことです。

Javaでは、main メソッドの String[] args が、その受け皿になります。

public static void main(String[] args)
{
    ...
}

args は文字列の配列です。

そのため、渡された値は次のように取り出せます。

書き方意味
args[0]1番目に渡された文字列
args[1]2番目に渡された文字列
args.length渡された引数の個数

たとえば、次のように実行したとします。

java MissionReader training3.txt

この場合、training3.txt が args[0] に入ります。

そして、次のように使えます。

new FileReader(args[0])

これにより、ファイル名をコードに固定せず、実行時に指定できるようになります。

実行コマンドargs[0]読み込むファイル
java MissionReader training3.txttraining3.txttraining3.txt
java MissionReader mission.txtmission.txtmission.txt
java MissionReader report.txtreport.txtreport.txt

ドラゴンボール風に言えば、任務装置を起動するときに、どの記録ファイルを読むかを指令札として渡すようなものです。

装置そのものを作り直さなくても、起動時の指令によって動きを変えられます。

args.length で引数を確認する

コマンドライン引数を使うときは、引数の個数を確認することが大切です。

たとえば、ファイル名を1つだけ受け取るプログラムなら、引数は1個であるべきです。

if(args.length != 1){
    System.out.println("読むファイル名を正しく指定してください。");
    System.exit(1);
}

この確認をしておけば、引数を入れ忘れたときや、余計な引数を渡したときに、早い段階で分かりやすく知らせられます。

実行例args.length判定
java MissionReader0不正
java MissionReader training3.txt1正しい
java MissionReader training3.txt extra2不正

ドラゴンボール風に言えば、任務開始前に「指令札がちょうど1枚あるか」を確認している状態です。

指令札がなければ、どの記録ファイルを読むのか分かりません。
指令札が多すぎても、この任務では扱いきれません。

だから、args.length による確認はとても大切です。

14章で身についたこと

14章で学んだ内容を整理すると、次のようになります。

テーマ学んだこと
例外処理try、catch、finally で異常時にも対応できる
例外クラス例外は Throwable 系列のクラスのオブジェクトである
独自例外自分で意味のある例外クラスを作れる
throw例外オブジェクトを実際に送出する
throwsメソッドが例外を送出する可能性を宣言する
RuntimeException明示的な例外処理が必須ではない例外がある
ストリーム入出力をデータの流れとして考える
標準入出力キーボードや画面とやり取りできる
ファイル出力データをファイルへ保存できる
ファイル入力保存済みのデータを読み込める
closeファイルを使い終わったら閉じる
コマンドライン引数実行時に外から値を受け取れる

こうして見ると、14章はかなり実践的な章です。

例外処理によって、プログラムは異常に備えられるようになりました。

入出力処理によって、プログラムは外部の世界とデータをやり取りできるようになりました。

コマンドライン引数によって、実行時に外から指示を受け取れるようになりました。

図:14章全体の流れ

この図が示していること

この図では、14章で学んだ内容を、例外処理、入出力処理、コマンドライン引数の3つに分けて整理しています。

左側の例外処理では、try、catch、finally、throw、throws、独自例外がまとまっています。

これは、異常が起きたときにどう受け止め、どう伝え、どう後片付けするかを表しています。

中央の入出力処理では、System.in、System.out、FileReader、FileWriter、BufferedReader、PrintWriter、close がまとまっています。

これは、プログラムが外部とどのようにデータをやり取りするかを表しています。

右側のコマンドライン引数では、String[] args、args[0]、args.length によって、実行時に外から値を受け取る流れを表しています。

図のまとまり意味
例外処理予想外の異常に備える
入出力処理外部とデータをやり取りする
コマンドライン引数実行時に外から指示を受け取る

この図から分かることは、14章が単なる細かな文法の集まりではなく、Javaプログラムを現実的に使いやすくするための章だったということです。

14章で得た力の意味

14章を学ぶ前は、プログラムは「書いた処理を順番に実行するもの」という見方が中心でした。

しかし、14章を通して、次の視点が加わりました。

新しく身についた視点内容
異常に備える例外が起きる可能性を考えて処理を書く
状況に応じて対応するcatch で問題を受け止める
後片付けを大切にするfinally や close で資源を整理する
外から受け取るキーボード、ファイル、引数からデータを受け取る
外へ出す画面やファイルへデータを出す
データを残すファイルへ保存してあとから使えるようにする
実行時に変えるコマンドライン引数で柔軟に動かす

これは、プログラムが「学習用の小さな例」から「実際に使える道具」へ近づいたということです。

ドラゴンボール風に言えば、戦士が技を覚えるだけでなく、任務中の異常に備え、本部と通信し、修行記録をファイルに残し、次の任務へ活かせるようになった状態です。

14章で学んだ例外処理と入出力は、これからさらに実用的なJavaプログラムを作るための重要な土台になります。