検索

キーワード


【Java】例外クラスの制御方法 3種類の基本と使い方を解説

  • 公開日:2021-01-26 08:21:20
  • 最終更新日:2021-01-26 08:20:06
【Java】例外クラスの制御方法 3種類の基本と使い方を解説

こんにちは、駆け出しエンジニアの伊藤です。

東京ITカレッジのJava研修で学んだ内容を復習も兼ねて記事にしたいと思います。

今回は、Javaの例外制御について、try - catch 構文、try - catch - finally構文、マルチキャッチなどを使った制御方法について解説していきます。

Javaやプログラムについて勉強し始めた方の参考になれば幸いです!

(eclipseを使用して計算を行っています)


関連記事:【Java】例外クラスの概要や使用実例を解説 ‐Exception、RuntimeException、Errorなど

     【Java】例外制御、例外の活用方法入門

     【Java】例外構文 3種類の活用方法解説

     【Java】独自例外を作るには?作成方法を解説



例外制御、例外処理について(検査例外)

Javaでは、プログラム実行中に想定外の異常(例外)の発生が考えられるところで、どのように対処するかを事前にプログラムに記述しないと、コンパイルエラーが起きてしまいます(検査例外については、関連記事に解説があります)。

その為、例外の発生が事前に起こると分かる場合は、エラーを回避するための構文が用意されています。


例外処理は、以下のいずれかの方法で記述する必要があります。

・try - catch でキャッチする

・throws句で例外を宣言する(呼び出し側で try-catch を実装する場合)


関連記事:【Java】例外クラスの概要や使用実例を解説 ‐検査例外・非検査例外など


try - catch 構文

例外が発生する可能性があるプログラムについては、事前に例外処理をするためのプログラムを記述する必要があります。

そのうちの1つが「try - catch」です。

try - catch は、例外が発生しうる箇所で、直接エラーの対処を記述する方法です。


<try - catch 構文>

try {
	例外が発生する可能性があるプログラム
} catch(例外クラス 変数名) {
	例外が発生した場合に実行されるプログラム
}


tryブロック  ‐例外が発生する可能性があるプログラムをtryブロックの中に記述します。

catchブロック‐実行時に例外を検出した場合には、chatchブロック内のプログラムが実行されます。

      catch の引数には、発生する可能性がある例外クラスと、変数名を記述します。

    (例外クラスの変数名は、任意です。しかし、 Exception(例外)の頭文字「e」または「ex」を変数名にあてることが多く見られます)


<try - catch の動き>

・検査例外(try-catch)では、コンパイル時に例外処理プログラムの記述があるかどうかのチェックが入ります。

・例外処理が書かれていないと、コンパイルエラーになります。

・例外処理が書かれている場合は、try部分が実行され、正常にいけば通常通りの結果となります。

・しかし、もし tryブロック内で例外の発生がJVMによって検知されると、catchブロックに移行し、例外が発生した場合のプログラムを実行します。


・エラーがなかった場合:try → 通常の結果

・エラーがあった場合 :try → エラーを発見! → try内のプログラムをエラー箇所で中断 → catchに移行 →catchブロック内のプログラムを実行する


詳しくは、以下のサンプルプログラムで見てみましょう。


 1 //IOExceptionサンプル1
 2 package exception.gaiyou;
 3
 4 import java.io.File;
 5 import java.io.FileWriter;
 6 import java.io.IOException;
 7
 8 public class IOException1 {
 9
10     public static void main(String[] args) {
11         System.out.println("-----実行結果-----");
12         try {
13             File file = new File("c:\\Sample\\Hello.txt");
14             FileWriter fw = new FileWriter(file);
15             //ファイルの書き込み
16             fw.write("Hello World \n");
17             fw.write("Bonjour le monde");
18             //ファイルを閉じる
19             fw.close();
20             System.out.println("ファイルへの書き込みに成功しました。");
21         //例外だった場合のキャッチ部分
22         } catch(IOException e) {
23             System.out.println(e);
24         }
25     }
26 }


-----実行結果-----
ファイルへの書き込みに成功しました。


上記のプログラムで作ったファイルを開くと writeの引数に指定した文字列が書き込まれているはずです。

FileWriterのコンストラクタでは、IOException例外が発生する可能性があるので、例外処理の記述が必要になります。

では、例外処理の try-catch を記述しないとどうなるのでしょうか?


 1 //IOExceptionサンプル2
 2 package exception.gaiyou;
 3
 4 import java.io.File;
 5 import java.io.FileWriter;
 6 import java.io.IOException;
 7
 8 public class IOException2 {
 9
10     public static void main(String[] args) {
11         System.out.println("-----実行結果-----");
12         File file = new File("c:\\Sample\\Hello.txt");
13         FileWriter fw = new FileWriter(file);
14         //ファイルの書き込み
15         fw.write("Hello World \n");
16         fw.write("Bonjour le monde");
17         //ファイルを閉じる
18         fw.close();
19         System.out.println("ファイルへの書き込みに成功しました。");
20     }
21 }


Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
	処理されない例外の型 IOException
	処理されない例外の型 IOException
	処理されない例外の型 IOException
	処理されない例外の型 IOException

	at exception.gaiyou.IOException2.main(IOException2.java:13)


FileWriterオブジェクトを作ろうとすると、「java.lang.Error」=重大なエラーが発生します。

ここでの重大なエラーとは、コンパイルエラーのことを指します。

結果のところには、「処理されない例外の型」として、「IOException」があります。

IOExceptionは、Exceptionクラス(検査例外)のサブクラスのため、例外処理の記述が必須なので、上記のようなコンパイルエラーが発生します。

(エクリプスでは、自動文法チェックが有効になっている場合、try-catchを記述しないと文法エラーが発生するため、コンパイルする前にエラーが分かります)


では、例外が発生した場合はどうなるでしょうか?

以下のサンプルプログラムでは、わざとエラーが発生するように書いています。


 1 //IOExceptionサンプル
 2 package exception.gaiyou;
 3
 4 import java.io.File;
 5 import java.io.FileWriter;
 6 import java.io.IOException;
 7
 8 public class IOException3 {
 9
10     public static void main(String[] args) {
11         System.out.println("-----実行結果-----");
12         try {
13             File file = new File("c:\\Sample\\Hello.txt");
14             FileWriter fw = new FileWriter(file);
15             //ファイルの書き込み
16             fw.write("Hello World \n");
17             fw.write("Bonjour le monde");
18             //ファイルを閉じる
19             fw.close();
20             //エラーを発生させるために記述
21             fw.write("ハロー・ワールド");
22             System.out.println("ファイルへの書き込みに成功しました。");
23         //例外だった場合のキャッチ部分
24         } catch(IOException e) {
25             System.out.println(e);
26         }
27     }
28 }


-----実行結果-----
java.io.IOException: Stream closed


IOException3サンプルでは、IOException2サンプルとは違い「-----実行結果-----」が表示されていることからも、一度は実行がされている(コンパイルエラーではない)ということが分かります。


IOException3サンプルでの try-catch の動きを日本語で追ってみます。

・上から順にプログラムが実行され、tryブロックが読み込まれファイルを作って開きます。
・writeの引数に指定した文字列をファイルに書き込みます。
・書き込みが終わったら、ファイルを閉じます。
・再度ファイルに書き込みをしようとした時に、ファイルが閉じられていて書き込みに失敗するため例外が発生します。
・例外発生を確認すると、実行中のtryブロックを中断し、catchブロックに移行します。
 移行する際は、「tryブロック内で何の例外が発生したか」の情報が catchブロックの変数名(上記サンプルプログラムでは e )に渡ります。
 変数名には、例外情報(IOExceptionインスタンス:ストリームはすでにクローズされています)が渡ります。
・結果には、例外発生の情報が表示されます。

「try の中で発生した例外に対して、catch の中で適切に対応する」というのが、try-catchになります。



関連記事:【Java】例外クラスの概要や使用実例を解説-検査例外、Exceptionクラスなど

     java.io.Fileクラスの使い方(ファイル一覧の取得、フィルタ)




try - catch - finally 構文

try-catch構文では、tryで例外があるかを試し、例外発生があった場合には catchブロックにその内容を渡しプログラムを実行しています。

その際、tryで発生した例外箇所以降のプログラムは実行されずスキップされ、catchブロックに移行してしまいます。


そのスキップされたプログラムの中身が、例外発生の有無に関わらず必ず実行しなければならないプログラムの場合があります。

例えば、プログラムで繋げたネットワークの切断、接続したデータベースの開放やファイルのクローズなどです。

繋げたり開いたりしたものは、必ず切断したり閉じたりすることがセットとなります。

その為、切断したり閉じたりするプログラムは必ず行われることを保証しなくてはいけません。

その時に使う例外処理が、「try-catch-finally」です。


<try - catch - finally 構文>

try {
	例外が発生する可能性があるプログラム
} catch(例外クラス 変数名) {
	例外が発生した場合に実行されるプログラム
} finally {
	例外の有無に関わらず必ず実行するプログラム
}


tryブロックと catchブロックの動きは、前章で解説した「try - catch構文」の動きと同じですが、ここに finallyブロックが入ると最後のプログラムの動きが変わってきます。


・finallyブロック‐例外が発生しても、しなくても、必ず最後に finallyブロックのプログラムが実行されます。


例外が発生せず正常にtryブロック内のプログラムが実行された場合でも、例外が発生しtryブロックからcatchブロックにプログラムが移行した場合でも、その後必ず finallyブロックのプログラムが実行されるのです。

このfinallyブロックを使うことで、切断したり閉じたりするプログラムが必ず実行されることを保証します。

詳しくは、以下のプログラムを見てみましょう。


 1 //IOException4 サンプル
 2 package exception.gaiyou;
 3
 4 import java.io.File;
 5 import java.io.FileWriter;
 6 import java.io.IOException;
 7
 8 public class IOException4 {
 9
10     public static void main(String[] args) {
11         System.out.println("-----実行結果-----");
12         FileWriter fw = null;
13         try {
14             File file = new File("c:\\Sample\\Hello.txt");
15             fw = new FileWriter(file);
16             //ファイルの書き込み
17             fw.write("Hello World \n");
18             fw.write("Bonjour le monde");
19             System.out.println("ファイルの書き込みに成功しました。");
20         //例外だった場合のキャッチ部分
21         } catch(IOException e) {
22             System.out.println("入出力処理の失敗、または割り込みの発生により例外が発生しました。");
23         } finally {
24             //ファイルを閉じる
25             if (fw != null) {
26                 try {
27                     fw.close(); 
28                 } catch (IOException e) {
29                     System.out.println(e);
30                 }
31             }  
32         }
33     }
34 }


-----実行結果-----
ファイルへの書き込みに成功しました。


IOException4サンプルで、ファイルのcloseへの対応が確実に実行されるようになりました。


finallyブロックは、例外の有無に関係なく実行されるため、例外があるかないかで以下の流れになります。

・例外の発生がない場合 : try → finally
・例外の発生がある場合 : try → catch → finally


IOException4サンプルでは、FileWriterの変数を try-catch-finallyの外側で宣言しています。

これは、tryブロックと finallyブロックの両方で、同じ変数を使用するためです。

もし、前章のIOException3サンプルのように tryブロック内で変数宣言を行ってしまうと、スコープの観点からfinallyブロックでの同一変数使用時にコンパイルエラーが発生します。

その為、変数宣言のみが try-catch-finallyに入っていないのです。


catchブロック内の例外コメントは、29行目のように変数名のままを出力するとJavaのベーシックなコメントが表示されます。

しかし、22行目のように日本語のメッセージを出力したいなど、任意にメッセージを変えることも可能です。


finallyブロック内でも try-catch を行っています。

これは、ファイルをクローズする際も、IOException例外が発生する可能性があり、try-catch をしないとコンパイルエラーが発生するからです。


また、finallyブロックでは if で null のチェックを行っています。

この if がなくても、コンパイルエラーは起こりません。

しかし、もし14行目でファイルを作る際に失敗になってしまうと、エラーが生じてしまいます。

ファイル作成に失敗すると、変数 fw には null が入った状態で、20行目の catchブロックに移行し例外メッセージが出力 → finallyブロックへと行きます。

finallyブロックは、例外の有無にかかわらず実行されるため、null の変数 fw に対して操作した場合にエラーが発生してしまうのです。


・・・

} finally {
    //ファイルを閉じる
    try {
        fw.close(); ← 閉じるファイルが存在しない!
    } catch (IOException e) {
        System.out.println(e);
    }
}  


閉じるファイルが存在するかしないかをチェックするために、finallyブロックで if による null チェックを行っているのです。

ファイル作成に成功 → fw は null では、なくなる
ファイル作成に失敗 → fw は null のまま


これで、エラーを回避したプログラムとなりました。

しかし、IOException4サンプルプログラムは、プログラム自体が冗長であったり、また try-catch を2度も行ったりしており、記述忘れなどが起こることがあります。


その為、JDK7から新たな構文が用意されました。

それが、「try-with-resources」です。

try-with-resources で、IOException4サンプルと同じ内容のプログラムを作ると、finallyブロック内で行った try-catch の記述が必要なくなります。

try-with-resources の解説は、関連記事で詳しく載せているので、ぜひご一読ください。


関連記事:【Java】例外構文 3種類の活用方法解説

     java.io.Fileクラスの使い方(ファイル一覧の取得、フィルタ)



マルチキャッチ

通常の例外処理

try-catch-finally では、tryブロックと finallyブロックは1つずつしか記述ができません。(複数記述するとコンパイルエラーが発生します)

しかし、catchブロックは複数の記述ができます。

つまり、複数の例外を書くことが出来るのです。


ただし、上記の複数の例外(catch)を記述する際には、条件があります。


・例外に継承関係がある場合、上位クラスを後に記述し、下位クラスをその前に記述する。


サンプルプログラムで見てみましょう。

 1 //MultiCatch1 サンプル
 2 package exception.gaiyou;
 3
 4 import java.io.File;
 5 import java.io.FileWriter;
 6 import java.io.IOException;
 7
 8 public class MultiCatch1 {
 9
10     public static void main(String[] args) {
11         System.out.println("-----実行結果-----");
12         FileWriter fw = null;
13         try {
14             File file = new File("c:\\Sample\\Hello.txt");
15             fw = new FileWriter(file);
16             //ファイルの書き込み
17             fw.write("Hello World \n");
18             fw.write("Bonjour le monde");
19         //例外だった場合のキャッチ部分
20         } catch(NullPointerException e) {
21             System.out.println("null により例外が発生しました。");
22         } catch(IOException e) {
23             System.out.println("入出力処理の失敗、または割り込みの発生により例外が発生しました。");
24         } catch(Exception e) {
25             System.out.println(e);
26         }
27     }
28 }


-----実行結果-----


サンプルプログラムのように、上位クラスを後に下位クラスを先に記述します。

その為、以下のようにして継承クラスにおける上位クラスを先に書くことはできません。

try{
・・・
} catch (Exception e) {
・・・
} catch (IOException e) {
・・・
}


Exceptionクラスは、IOExceptionクラスのスーパークラスです。

もし、Exceptionクラスを先に記述してしまうと、Exceptionクラスに含まれるサブクラスすべての例外クラスを catchすることになってしまい、その後に記述されたIOExceptionクラスの catch句が到達不可能なコードとなり、コンパイルエラーが発生してしまいます。

複数のcatchを使用する場合は、例外クラスの上位関係に気を付けましょう。

※例外の上位関係については、JavaDocに載っています。


関連記事:継承とオーバーライド



マルチキャッチ

1つのプログラムで複数例外をcatchできるのは、助かります。

しかし、catch内のプログラム内容が一緒だった場合、同じプログラムを何度も書かなくてはいけなくなり、コード量が増えてしまいます。

その為、JDK7から「マルチキャッチ」という構文ができました。


複数の例外で同じプログラム内容の場合や親クラスが違う例外クラスをまとめる場合、1つのcatchブロックで複数の例外クラスをキャッチすることが出来ます。


<マルチキャッチ 構文>

try {
	例外が発生する可能性があるプログラム
} catch(例外クラス | 例外クラス 変数名) {
	例外が発生した場合に実行されるプログラム

列記するクラスを(・・・ | ・・・)パイプで区切ることで、例外クラスのプログラムをひとまとめにすることができます。

マルチキャッチの注意点です。

・変数名は、最後の例外クラスに記述する。
・同じ例外クラス(IOException | IOException e のようなこと)を複数記述することはできない。
・継承関係にある例外クラスを列記することはできない。
・キャッチした参照変数は暗黙的に final になる。


サンプルプログラムで見てみましょう。

 1 //MultiCatch2 サンプル
 2 package exception.gaiyou;
 3
 4 import java.io.File;
 5 import java.io.FileWriter;
 6 import java.io.IOException;
 7
 8 public class MultiCatch2 {
 9
10     public static void main(String[] args) {
11         System.out.println("-----実行結果-----");
12         FileWriter fw = null;
13         try {
14             File file = new File("c:\\Sample\\Hello.txt");
15             fw = new FileWriter(file);
16             //ファイルの書き込み
17             fw.write("Hello World \n");
18             fw.write("Bonjour le monde");
19         //例外だった場合のキャッチ部分
20         } catch(NullPointerException | IOException e) {
21             System.out.println("入出力処理の失敗、または割り込みの発生か、null により例外が発生しました。");
22         } catch(Exception e) {
23             System.out.println(e);
24         }
25     }
26 }


-----実行結果-----


マルチキャッチの注意点から、以下のプログラムでは、エラーが発生してしまいます。

try {
・・・
} catch (NullPointerException | IOException | Exception e) {
	System.out.println(e);
}


上記は、継承関係にあるIOExceptionクラスとExceptionクラスが列記されているためです。

継承関係にあたる例外クラスについては、サンプルプログラムのように分けて catch する必要があります。


マルチキャッチでは、細かいエラーの内容を示すことはできません

1つの catchブロックで複数の例外クラスをキャッチするため、エラー内容は同一の物となります。

どうしてもcatchブロック内のプログラムを1つひとつ変えたい場合は、catchブロックを何個も書いていく通常の例外処理を行う必要があります。


また、マルチキャッチの動きは、通常の例外処理と同じです。

コンパイル後は、マルチキャッチで記述したとしてもMultiCatch1 サンプルのように、通常の例外処理の形に変換され、実行されているのです。

その為、例外クラスの上位関係において、マルチキャッチ構文でも書く順番はサブクラスからとなっています。



また、try-finally だけで記述することもできます。(catchブロックを省略することも可能)

例外がスローされる(投げられる)可能性のあるメソッドであることを宣言(throws)した場合に、catchブロックの省略ができます。

throws、throw に関しては、関連記事で解説してるので、ぜひご一読ください。


関連記事:【Java】例外制御、例外の活用方法入門

     継承とオーバーライド



まとめ

この記事では、例外制御として、try-catch、try-catch-finally、マルチキャッチなどを中心に解説しました。

例外制御には、他にもthrows、throwなどもありますので、関連記事から一読していただくと、例外への理解がより深まるでしょう。

関連記事:【Java】例外クラスの概要や使用実例を解説 ‐Exception、RuntimeException、Errorなど

     【Java】例外制御、例外の活用方法入門

     【Java】例外構文 3種類の活用方法解説

     【Java】独自例外を作るには?作成方法を解説


【著者】

伊藤

Javaを研修で3か月学んだ、駆け出しのエンジニアです。
現在は、ベンダー資格を取得するため、勉強を日課にできるよう努力中です。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様