例外処理

メソッドにthrowsする記述があるということは、 そのメソッド呼び出し側に例外処理を任せる意味となります。 過去の例を示します。

import java.io.FileInputStream;// バイナリファイル読み取りストリーム用クラス
public class Dump16{
	public static void main(String[] arg)throws Exception{
		//ファイルから入力するストリーム取得
		FileInputStream fpr = new FileInputStream( arg[0] );
		int pos = 0;	// ファイル内位置管理用(position )
		int c; 		// 1byte読み取り値記憶用
		while ((c = fpr.read()) != -1){
			if (pos % 16 == 0){
				System.out.printf("\n%04X ", pos);//アドレス表示用
			}
			System.out.printf("%02X ", c);/* 表示*/
			pos++;
		}
		fpr.close();
	}
}

FileInputStreamのコンストラクタ内部ではFileNotFoundExceptionをthrowsしており、 readメソッド内部ではIOExceptionをthrowsしていいます。
(メソッド内部でそれぞれの例外クラスのオブジェクトを生成してthrowsしている部分があるということです。)
そして、上記例ではこれらのスーパークラスであるExceptionをthrowsする指定により メソッド呼び出しへ投げています。
IOExceptionのオブジェクトがスーパークラスの変数で管理できることと同じ理由で、 IOExceptionのスーパークラスであるException がthrowsで指定されています。
しかし起き得る例外だけを外部に任せるという意味で、本来はIOExceptionをthrowsすべきでしょう。

何れにしても投げられた例外のオブジェクトをキャッチして例外処理部に移動する処理を書かないと、 例外オブジェクトのthrowに対する実行停止を最終的に避けることはできません。
その例外処理部がtry〜catch〜finalを使う方法でその書き方は次の通りです。

このtryブロック内で発生した例外に対応するcatchブロックで 処理することで、実行停止することなしに進めるプログラムが作れます。 (try内の処理を中断し、対応するcatchブロックに移動します。catchブロックの内容を処理した後、 fainallyブロックに進みます。)

catchブロックは、例外の種類が違えば一個以上でいくつ作っても構いません。 しかし記述する時の並べる順番を、注意しなければなりません。
tryブロック内で発生した例外に対応するcatchブロックを探す順番は、 上のcatchブロックから順番に行われ、 発生した例外のcatchの括弧内引数にある『Exceptionの継承クラス』に適応するブロックが見つかった段階で、 そのブロック内を処理します。
そしてcatchブロック処理後、 下のcatchブロックをチェックせずにfinallyブロックへと進むことになっています。
この適応の判定は、catch括弧内引数に例外のインスタンスが記憶可能かの判定です。
例えば、Exceptionは、全の例外クラスのスーパークラスになるので、どんな例外のインスタンスでも記憶です。よって、このcatchブロックを先に記述すると、その後にあるcatchブロックを処理することは出来なくなります。 つまり、catchブロックの順番を考える場合は、 例外クラスの継承構造を知り、 より詳しい特化した例外サブクラスのcatchブロックから順番に書く ことになります。
前述のプログラムに対して例外処理を書いた例を示します。 (例外による実行停止を避けるプログラムではありませんが、オペレータに使い方の誤りを指摘しています)

import java.io.FileInputStream;// バイナリファイル読み取りストリーム用クラス
import java.io.IOException;//入出力処理の失敗の例外クラス
public class Dump16{
	public static void main(String[] arg){
		FileInputStream fpr=null;
		try {
			fpr = new FileInputStream(arg[0]); //ファイルから入力するストリーム取得
		}
		catch (ArrayIndexOutOfBoundsException e){
			System.out.println("コマンドラインパラメタでファイルを指定して使います。");
			System.exit(1);
		}
		catch (java.io.FileNotFoundException e)	{
			System.out.println(arg[0]+"のファイルが見つかりません。");
			e.printStackTrace();
			System.exit(1);//【1】
		}
		int pos = 0;	// ファイル内位置管理用(position )
		int c; 		// 1byte読み取り値記憶用
		try{
			while ((c = fpr.read()) != -1){
				if (pos % 16 == 0){
					System.out.printf("\n%04X ", pos);//アドレス表示用
				}
				System.out.printf("%02X ", c);// 表示
				pos++;
			}
		}
		catch(java.io.IOException e){
			throw new java.lang.RuntimeException("readの読み込み処理で失敗");
		}
		finally {// 直前のtry { } の後に必ず実行する範囲がこのfinallyブロック
			try {
				fpr.close();
			}
			catch (IOException e){
				e.printStackTrace();
			}
		}
	}
}

上記では、3つ(try{ }catch( ){ }try{ }catch{ } finally { }try{ }catch(){ } )の例外を調べる部分と処理を書いています。
最初のtry{ }catch( ){ }では、ArrayIndexOutOfBoundsExceptionと、FileNotFoundException の例外を想定して書いています。
コマンドラインでファイル名の指定がない場合、mainの引数argの配列でarg[0]の要素が存在 せずに、ArrayIndexOutOfBoundsExceptionの例外が発生します。 またそのファイル名のファイルが無い場合は、FileNotFoundExceptionの例外で、 それぞれ分かりやすいメッセージを出すように変更しています。なお、 printStackTraceは、 これまでのエラー停止で表示する内容と同じ内容のメッセージ表示です。

try{ }catch( ){ }で、readメソッドの入出力例外を書いています。  このcatchで、RuntimeExceptionを生成して直接にthrowしていますが、  これをtryの中に書くことは少ないでしょう(それも直後のcatchに捕られます)。  一般にthrowする場合はそれを記述したメソッドの呼び出し側で処理させるための書き方となります。
また、Exceptionの継承クラスを作り、それをthrowする使い方もあります。

なおfinally { }の中で、try{ }catch( ){ }を書いています。
これは、既にfprがオープン済みなので、エラーがあった場合でも必ずクロースさせるために、 fpr.close();を記述しているのですが、このclose()メソッドが内部にIOExceptionのthrow部分があるので、 ここにもtry〜catchが必要になっているのです。
通常は、ここまで細かくtryを分割する必要がなく、全体をtryで囲み、 最後にエラー処理を並べるだけでのことが多いでしょう。
上記プログラムで、いくつかの実行エラーを示しします。
最初の例は、表示するファイル名を指定し忘れた場合の動作です。

D:\java>java Dump16
コマンドラインパラメタでファイルを指定して使います。

D:\java>

次の例は、表示するファイル名を間違えて存在しないファイルを指定した場合の動作です。

D:\java>java Dump16 transact3.bin
transact3.binのファイルが見つかりません。
java.io.FileNotFoundException: transact3.bin (指定されたファイルが見つかりません。)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.<init>(FileInputStream.java:106)
        at java.io.FileInputStream.<init>(FileInputStream.java:66)
        at Dump16.main(Dump16.java:10)
        
D:\java>

java.io.FileNotFoundException〜の部分が、 printStackTraceメソッドの表示部分です。
この表示で実行が終了する訳ではありません。
実行を停止させているのは、System.exit(1);//【1】の部分です
もしこの部分を削除すると、readの処理と進み、そこで、nullを参照したため NullPointerExceptionが投げられています。しかし、NullPointerExceptionのcatchは書いていません。 しかし、finally部があるのでそれが実行されます。そして、closeで再びNullPointerExceptionが投げられて います。

D:\java>java Dump16 transact3.bin
transact3.binのファイルが見つかりません。
java.io.FileNotFoundException: transact3.bin (指定されたファイルが見つかりません。)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.<init>(FileInputStream.java:106)
        at java.io.FileInputStream.<init>(FileInputStream.java:66)
        at Dump16.main(Dump16.java:10)
        
Exception in thread "main" java.lang.NullPointerException
        at Dump16.main(Dump16.java:45)
        
D:\java>      

よって、【1】のSystem.exit(1)は、必要な記述です。
なお、NullPointerExceptionのcatchは、通常書きません。何故ならこれはプログラマーのミスによって 起きる部分であることが普通だからです。一般にこれら例外処理はオペレータや、 汎用クラスの利用者に対する処理を書くのが普通です。

オペレータミスによる実行停止の回避例

オペレータの入力ミスに対応する例を以下に示します。(例外で実行が停止するのを回避しています)
これは、入力する数字の平方根を表示するプログラムですが、誤った数字以外の入力時に 例外処理を利用することで再入力させています。   部分が例外の起こりえる箇所です。

import java.util.Scanner;

public class Test
{
	public static void main(String[] arg)	{
		Scanner stdin = new Scanner(System.in);
		String s = null;
		boolean success=false;
		while(success == false){
			try {
				System.out.print("数値>");
				s = stdin.nextLine();
				double d = Double.parseDouble(s);

				System.out.println(s +"の平方根は" + Math.sqrt(d) +"です");
				success = true;//ここで繰り返しを止める制御をしています。
			}
			catch (NumberFormatException e)
			{
				System.out.println("正しく数字を入力ください。");
				System.out.print("再入力 ");
			}
			catch (Exception e)
			{
				System.out.println("予期しなかったエラーです。");
				e.printStackTrace();
			}
		}
	}
}

以下実行例では、最初の入力に数字でない入力がありますが、再入力させています。

D:\java>java Test
数値>100a
正しく数字を入力ください。
再入力 数値>100
100の平方根は10.0です

D:\java>