Javaの「try-catch文」は、プログラム中のソースコードをtryして(試して)、異常発生時に「例外」とcatchして(捉えて)、エラー処理をするための機能です。実はtry-catch文を使わなくても、Javaのプログラムは基本的に書けます。しかし、try-catch文が必要となるケースもあり、何よりも安全で使いやすいプログラムを作るために重要な機能です。
Javaでtry-catch文でエラー処理を行うことを、「例外処理」や「例外制御」と呼びます。例外処理を行うことで、仮にバグがあったとしても正しい処理ができるような、「冗長性」と「柔軟性」が高いプログラムが書けるようになるでしょう。本記事では、Javaのtry-catch文の概要や機能、具体的な使い方と応用テクニックを解説します。
目次
Javaの「try-catch文」は、プログラムの実行時に発生するさまざまなエラーを、適切に処理するための機能です。Javaではプログラム中で何らかのエラーが発生した場合、その問題の種類や原因などの情報を、「例外」という形式でプログラマに通知します。
try-catch文を書くことで、プログラムをまずtryして(試して)、例外が発生したときにそれをcatchして(捉えて)、例外の種類に応じたエラー処理が行えるようになります。一連の作業を「例外処理(例外制御)」と呼び、これを行うことで「エラーやバグに強いプログラム」が書けるようになるのです。
また、try-catch文に加えて「finally」も付け足して、例外の有無に関わらず最後に必ず実行される処理を記述することもできます。try-catch文とfinallyは、大まかに下記のように使えるので、まずは「こんな感じ」だというのを掴んでおきましょう。
try {
例外が発生する可能性がある処理内容; } catch(例外オブジェクトの型 引数) { 例外が発生した場合に対処するための処理内容; } finally { 例外の有無に関わらず最後に実行したい処理; } |
処理をtry-catch文で包み込む、というイメージを持つとわかりやすいでしょう。前述したように、「finally」の部分はなくても構いません。また、Javaはtry-catch文を使わなくても、ほとんどの場合はプログラム自体を書けますが、要所で挿入することによってプログラムの品質が大幅に高まります。
Javaでは、try-catch文を書かなくてもプログラムは動きます。仮に例外が発生した場合でも、プログラムが異常終了するだけなので、個人でプログラミングするぶんには問題ありません。しかし業務システムやアプリなどの場合は、プログラムがエラーで終了するなどというのは、決してあってはならない事態です。
業務用プログラムは、ビジネスの継続に欠かせないものであり、正常に使えなければ企業に多大な損害を与えてしまいます。そこでtry-catch文で例外処理を行うことにより、仮にエラーが発生した場合でも代替の処理、たとえばエラーの発生個所をスキップする処理が行えるようになります。結果的にプログラムの異常終了を回避でき、安全なプログラムが作れるのです。
Javaのtry-catch文を使いこなすために、まず下記6つの知識・基本テクニックを身につけておきましょう。
まずは、Javaのtry-catch文の基本的な使い方・実装方法を確認しておきましょう。前述したように、tryブロック(try節)には「例外発生の可能性がある処理」を記載し、catchブロック(catch節)には「例外発生後の処理(例外処理)」を記載します。
なお、catchブロックでは引数として「例外オブジェクト」を受け取ります。この例外オブジェクトを参照することで、例外の詳しい情報を確認できます。本章では、実際にサンプルプログラムを確認して、Javaのtry-catch文の雰囲気を掴みましょう。
//サンプルプログラム |
public class Main {
public static void main(String[] args) { // 要素数が「1」のint型配列を生成する int[] nums = new int[1]; // 例外が発生する可能性がある処理を行うため、try-catch文で囲む try {
// 配列の範囲外のインデックスを指定するため、ここで例外が発生する nums[1] = 1; // すぐにcatch文へ移行するため、下記の文章は表示されない System.out.println(“正常終了しました”); } catch (ArrayIndexOutOfBoundsException e) {
System.out.println(“例外が発生しました”); e.printStackTrace();
} finally {
System.out.println(“プログラムを終了します”);
} } } |
//実行結果 |
例外が発生しました
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1 at Main.main(Main.java:10) プログラムを終了します |
上記のサンプルプログラムでは、配列の要素外にあえてアクセスし、「ArrayIndexOutOfBoundsException」を発生させています。try-catch文がなければ、範囲外にアクセスした時点で、プログラムが異常終了します。しかし、try-catch文を入れたことにより、エラーが発生してもプログラムの実行を継続できることがポイントです。
上記のプログラムで配列生成時の要素数を変更すると、実行結果も大きく変わります。たとえば、配列サイズを「10」など大きな値に変えると、実行結果は「正常終了しました」となり、例外はいっさい表示されません。
Javaの例外処理では、複数の例外オブジェクトをcatchすることができます。やり方は簡単で、下記のようにcatch節を並べていくだけです。
try {
// 例外が発生する可能性がある処理; } catch (例外クラス1 変数名) { // 例外発生時の処理1; } catch (例外クラス2 変数名) { // 例外発生時の処理2; } catch (例外クラス3 変数名) { // 例外発生時の処理3; } |
if-else文を書くときのようなイメージで、処理を次々につなげていけばOKです。実際に、複数の例外オブジェクトをcatchするサンプルコードをご紹介します。
//サンプルプログラム |
import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) { try {
// 「FileNotFoundException」が発生する可能性あり FileReader fileReader = new FileReader(“Test.txt”); // データ読み込み用バッファを生成する char[] text = new char[256];
// 「IOException」が発生する可能性あり fileReader.read(text);
// ファイルから読み込んだテキストを画面上に表示する System.out.println(text); } catch (FileNotFoundException e) { // 1つ目の例外処理 System.out.println(“ファイルを開けません”);
} catch (IOException e) {
// 2つ目の例外処理 System.out.println(“ファイルを読み込めません”);
} } } |
//実行結果 |
ファイルを開けません |
上記のサンプルプログラムは、「Test.txt」というテキストファイルを開き、最大256文字まで読み込んで画面上に表示する簡易的なシステムです。しかし、要のテキストファイルがないため例外が発生します。
「FileNotFoundException」は、ファイルが見つからない場合に発生する例外です。プログラムのプロジェクトフォルダに「Test.txt」を作成すると、この例外は発生せずプログラムは正常に終了します。一方で「IOException」は、ファイル読み込み時に何らかの致命的エラーが起きたときに発生する例外です。
実は、「FileNotFoundException」や「IOException」が発生する可能性がある処理は、必ずtry-catch文を書かないといけません。これは2つの例外が「検査例外」に属するためですが、検査例外については後ほど改めて解説します。
「InputStream」「OutputStream」「BufferedReader」などのリソースは、プログラム終了前に「閉じる(リソースを解放する)」必要があります。そのためには、finallyブロックで「closeメソッド」を呼び出すのも有効です。
しかし、finallyブロック内でもtry-catch文を使う必要があるなど、何かと冗長なプログラムになってしまいます。そこでtry-with-resourcesを活用すると、自動的にリソースが閉じられるので便利です。下記のサンプルコードを見ていきましょう。
//サンプルプログラム |
import java.io.BufferedReader;
import java.io.File; import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) { // 「Test.txt」ファイルを開く(例外処理を確認するために、存在しないファイルを指定する) File file = new File(“Test.txt”); // 「try-with-resources」を活用し、FileReaderとBufferedReaderのリソースが自動的に閉じられるようにする try (FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader)) { // テキストファイルの文字列を読み込んで、画面上に表示する String str; while ((str = bufferedReader.readLine()) != null) { System.out.println(str); } } catch (IOException e) { // 途中で例外が発生した場合は、例外の内容を表示する e.printStackTrace(); } finally { System.out.println(“プログラム終了!”); } } } |
//実行結果 |
java.io.FileNotFoundException: Test.txt (指定されたファイルが見つかりません。)
at java.base/java.io.FileInputStream.open0(Native Method) at java.base/java.io.FileInputStream.open(FileInputStream.java:216) at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157) at java.base/java.io.FileReader.<init>(FileReader.java:75) at Main.main(Main.java:12) プログラム終了! |
やや複雑なプログラムですが、注目すべきポイントはtryブロックの部分で、「FileReader」と「BufferedReader」オブジェクトをカッコの中で生成していることです。こうすることにより「try-with-resources」が有効になり、例外の有無に関わらず、プログラム終了前にリソースが自動的に解放されます。
なお、今回のように複数のリソースをtry-with-resourcesで生成する場合は、「;(セミコロン)」で区切る必要があります。
これまではプログラムが発生させた例外オブジェクトを受け取り、例外処理を行う方法を解説しました。一方で、自分自身で意図的に例外を発生させることもできます。下記のような構文で「throw」キーワードを使い、例外オブジェクトを生成させましょう。
throw new 例外オブジェクトの型(“例外メッセージ”); |
「throw」は投げるを意味し、例外を発生させることを一般的に「例外を投げる」といいます。上記の構文は、通常のオブジェクトと同じように例外オブジェクトを生成し、コンストラクタでメッセージを設定するという意味合いがあります。それをthrowで投げるのです。throwは下記のサンプルコードのように、例外をthrowした直後で例外処理を行うときに使います。
//サンプルプログラム |
public class Main {
public static void main(String[] args) { // 割り算を行ってその答えを表示する System.out.println(“演算結果:” + divide(1, 0)); } public static int divide(int numA, int numB) { try {
// ゼロ除算が行われたときに例外を発生させる if (numB == 0) { throw new ArithmeticException(“ゼロ除算が行われました”); } return numA / numB; } catch (ArithmeticException e) {
e.printStackTrace(); return -1;
} } } |
//実行結果 |
java.lang.ArithmeticException: ゼロ除算が行われました
at Main.divide(Main.java:13) at Main.main(Main.java:5) 演算結果:-1 |
tryブロック内で例外が発生しそうな処理を行い、条件分岐で例外を発生させることがポイントです。なお、throwキーワードを使用した場合は、基本的にその直後に例外処理を行う必要があります。
つまり、throwキーワードをメソッド内で使った場合、例外処理は呼び出し元ではなくメソッド内で行うということです。呼び出し元で例外処理を行うようにしたい場合は、このあとで紹介するように「throws」キーワードを使いましょう。
なお、上記のプログラムで使用している「ArithmeticException」は、算術演算やキャスト演算などでエラーが発生したときに投げる例外オブジェクトです。
先ほどの「throw」は、例外オブジェクトを投げるメソッド内で例外処理を行うときに使います。一方でこちらの「throws」は、メソッドの呼び出し元で例外処理を行うためのものです。基本的には、「throwはメソッド内部で例外処理」「throwsはメソッド外部で例外処理」と覚えておきましょう。サンプルコードをご紹介します。
//サンプルプログラム |
public class Main {
public static void main(String[] args) { try { // 割り算を行ってその答えを表示する System.out.println(“演算結果:” + divide(1, 0)); } catch (ArithmeticException e) { e.printStackTrace(); } finally { System.out.println(“プログラムを終了します”); } } public static int divide(int numA, int numB) throws ArithmeticException { return numA / numB; } } |
//実行結果 |
java.lang.ArithmeticException: / by zero
at Main.divide(Main.java:21) at Main.main(Main.java:7) プログラムを終了します |
メソッド呼び出し元の処理は、冒頭でご紹介した手順と同じです。例外が起きる可能性がある処理をtry節で包み、catch節で例外オブジェクトを受け取って例外処理を行います。重要なポイントはメソッドの定義方法です。「戻り値の型 メソッド名(引数) throws 例外オブジェクトの型」という構文で書くと、メソッドが例外を投げるようになります。
一度catchした例外を再びthrowすることもできます。たとえば、例外が発生したその場では単にログだけ出力し、呼び出し元で詳しいエラー処理を行いたい場合などに便利です。再度throwする場合は、下記のサンプルコードのようにcatch節の内部で例外をthrowしましょう。
//サンプルプログラム |
import java.io.File;
import java.io.FileReader; import java.io.IOException; import java.io.UncheckedIOException; public class Main { public static void main(String[] args) { try {
// 例外を発生させるテスト用メソッドを呼び出す test();
} catch (UncheckedIOException e) {
e.printStackTrace();
} finally { System.out.println(“プログラムを終了します”); } } public static void test() throws UncheckedIOException { // テスト用ファイルを開く(例外処理を見るため、あえて存在しないファイルを指定します) File file = new File(“Test.txt”); // 「try-with-resources」を活用してリソースを生成する try (FileReader fileReader = new FileReader(file)){
int data; while ((data = fileReader.read()) != -1) { System.out.print((char) data); } } catch (IOException e) {
// エラーメッセージだけ表示して、例外を再び投げる // ただし、単に例外を投げるとメソッド呼び出し元で原因を特定できないため、再度throwする例外に新たな情報を持たせる System.out.println(“「IOException」が発生しました”); throw new UncheckedIOException(e);
} } } |
//実行結果 |
「IOException」が発生しました
java.io.UncheckedIOException: java.io.FileNotFoundException: Test.txt (指定されたファイルが見つかりません。) at Main.test(Main.java:40) at Main.main(Main.java:12) Caused by: java.io.FileNotFoundException: Test.txt (指定されたファイルが見つかりません。) at java.base/java.io.FileInputStream.open0(Native Method) at java.base/java.io.FileInputStream.open(FileInputStream.java:216) at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157) at java.base/java.io.FileReader.<init>(FileReader.java:75) at Main.test(Main.java:28) … 1 more プログラムを終了します |
重要なポイントは、単に「throw e」と例外を投げるのではなく、「throw new UncheckedIOException(e);」のように新しい例外オブジェクトを生成し、コンストラクタで「e」を引き渡していることです。このようにすると、呼び出し元に例外情報が伝わるので、適切な例外処理が行いやすくなります。
これまでのサンプルコードでもご紹介したように、ひとくちに例外オブジェクトといっても、さまざまな種類があります。しかし、よく見かける例外というものはあるので、大まかに覚えておくと良いでしょう。Javaでよく見かける例外には、下記のようなものがあります。
例外クラス | 例外の意味 | 具体例 |
java.lang.NullPointerException | ヌルポインタへのアクセス | 値が「null」のオブジェクトにアクセスした |
java.lang.IndexOutOfBoundsException | 配列の範囲外アクセス | 配列のインデックスの値が配列のサイズ以上 |
java.lang.IllegalArgumentException | 不適切な引数 | メソッドの引数に不適切なものを指定した |
java.lang.IllegalStateException | 不正な状態 | 未初期化でのメソッド呼び出し |
java.lang.ArithmeticException | 不正な算術計算 | ゼロ除算など |
java.lang.NumberFormatException | 不正な数値型への変換 | 変換元の文字列が数値ではないとき |
java.io.FileNotFoundException | ファイルが見つからない | 存在しないファイルを指定したとき |
java.io.IOException | 不正な入出力 | ファイルが存在しない場合など |
ちなみに「java.lang.NullPointerException」は、インターネットスラング「ぬるぽ」の語源となった例外です。これは下記のサンプルプログラムのように、値に「null」を指定したオブジェクトにアクセスしたときに発生します。
//サンプルプログラム |
public class Main {
public static void main(String[] args) { // 配列変数を初期化せずにヌルポインタを代入する int[] nums = null;
try {
// 配列変数にnullが入っているにも関わらず、配列の要素にアクセスしているため例外が出る nums[0] = 1;
} catch (NullPointerException e) {
System.out.println(“「ぬるぽ」が出ました!”); e.printStackTrace();
} finally { System.out.println(“プログラムを終了します!”); } }
} |
//実行結果 |
「ぬるぽ」が出ました!
java.lang.NullPointerException: Cannot store to int array because “nums” is null プログラムを終了します! at Main.main(Main.java:10) |
そもそもオブジェクトには、実体ではなく「アドレス」が入っています。オブジェクトの値が「null」というのは、オブジェクトがどの場所も指定しておらず、アクセスできる場所がないということ。そのため、nullなオブジェクトにアクセスしようとすると、NullPointerExceptionが出るのです。
Javaで適切な例外処理を行うためには、try-catch文をうまく使いこなすことが重要です。そのために、下記4つの注意点を確認しておきましょう。
例外をcatchしておきながら、例外処理を何も行わないのは極めて危険です。こうした行為を「例外の握りつぶし」と呼ぶことがありますが、たとえば下記のようなプログラムです。
try {
// 例外が発生する可能性がある処理; } catch (Exception e) { // catchだけして何もしない! } |
なお、下記のように「printStackTrace」だけを使うのも良くありません。本記事に掲載しているサンプルプログラムも、簡潔化のためにこのようになっていますが、本来は適切なエラー処理を行うことが望ましいです。
try {
// 例外が発生する可能性がある処理; } catch (Exception e) { e.printStackTrace(); //例外のメッセージを表示しているだけで中身がない! } |
printStackTraceを使えば例外の概要がわかりますが、「何もしない」のは非常に危険なので注意してください。なぜなら、例外を握りつぶすと下記のプログラムのように、例外が発生したことさえ認識できないからです。
//サンプルプログラム |
public class Main {
public static void main(String[] args) { // 配列変数を初期化せずにヌルポインタを代入する int[] nums = null;
try {
// 配列変数にnullが入っているにも関わらず、配列の要素にアクセスしているため例外が出る nums[0] = 1;
} catch (NullPointerException e) { // 例外を握りつぶす! }
System.out.println(“プログラムを終了します!”); }
} |
//実行結果 |
プログラムを終了します! |
上記のサンプルプログラムは、エラーメッセージなどが何も表示されないため、あたかも正常に終了したように見えてしまいます。しかし実際には、ヌルポインタへのアクセスという、致命的な問題があるのです。このようなプログラムなら、try-catch文を書かないほうが、プログラムが異常終了するという観点からまだマシだといえるでしょう。
Javaで例外処理を行うときは、tryブロックの中身はできるだけ簡潔に記載するほうが良いと考えられています。とくに下記のように、例外が発生する可能性がある処理や発生しない処理を混在させると、プログラムの流れが追いづらくなるので注意が必要です。
//サンプルプログラム |
import java.io.FileNotFoundException;
import java.io.IOException; public class Main { public static void main(String[] args) { // 下記のように、例外が発生する処理・発生しない処理をtryブロック内に混在させるのは良くない! try { methodA(); // 例外が発生する可能性がある処理 methodB(); // 例外が発生しない処理 methodC(); // 例外が発生する可能性がある処理 } catch (FileNotFoundException e) { // methodAの例外処理 e.printStackTrace(); } catch (IOException e) { // methodCの例外処理 e.printStackTrace(); } } public static void methodA() throws FileNotFoundException { throw new FileNotFoundException(“テスト例外A”); } public static void methodB() { // このメソッドは例外を発生させない } public static void methodC() throws IOException { throw new IOException(“テスト例外B”); } } |
//実行結果 |
java.io.FileNotFoundException: テスト例外A
at Main.methodA(Main.java:24) at Main.main(Main.java:10) |
上記のサンプルプログラムでは、try節の内部に「methodA」「methodB」「methodC」の3つの処理が記載されていますが、methodBは例外を出しません。tryブロックの中では、例外を出す処理だけ書くほうがわかりやすいので、try-catch文をわかるほうが良いでしょう。
これまで解説してきたように、Javaで例外処理(例外制御)を行うときは、catchブロック(catch節)で例外オブジェクトを受け取ります。しかし、例外オブジェクトにはさまざまな「型」があり、適切なものを指定しないと例外をcatchできません。そもそも例外オブジェクトとは、「Throwable」という特別なクラスを継承した「サブクラス」なのです。
例外オブジェクトの系統 | 代表的な例外オブジェクト | |
Error | StackOverflowError
OutOfMemoryError ClassFormatError など |
|
Exception | Runtime Exception系列 | NullPointException
ArrayIndexOUtOFBOUndsExeception IllegalArgumentException など |
その他の例外オブジェクト | IOException
ClassNotFoundException など |
スーパークラスの「Throwable」は、「Error」と「Exception」というサブクラスを有します。さらに、Exceptionには「RuntimeException」というサブクラスがあるなど、例外オブジェクトの継承関係は複雑です。一方で、「Errorは通常のアプリではcatchすべきではない」ので、基本的にはException系列の例外だけ注目すればOKだといえます。
つまり、どの例外が発生するかは処理の内容によりますが、catch節で「Exception」を指定すれば、大半の例外オブジェクトをcatchできるということです。そのため、初心者のうちは例外オブジェクトの型を意識するよりも、とりあえずExceptionを指定するのでも良いでしょう。
「try-catch文」は、基本的には使わなくてもプログラム自体は可能です。しかし、Javaの例外には「検査例外」と「非検査例外」の2種類があり、それぞれ下記のような特徴があります。
検査例外 | 非検査例外 | |
意味 | catchしなければコンパイルエラーになる例外 | catchしなくてもコンパイルエラーにならない |
どのような例外か | 呼び出し側で回避できない例外 | 呼び出し側で回避できる例外 |
具体的な例外オブジェクト | NullPointerException
ArrayIndexOutOfRangeException IllegalArgumentException など (Runtime Exception系列以外の例外) |
IOException
InterruptException ParseException など (Runtime Exception系列の例外) |
たとえば、ヌルポインタへのアクセスで発生するNullPointerExceptionは、呼び出し時に「nullチェック」を行えば回避できます。一方で、ディスクの容量不足や故障などで発生する「IOException」は、基本的には呼び出し側で回避できない異常です。
ちなみに「SimpleDateFormat.parseメソッド」は、呼び出し元で回避可能であるにも関わらず、検査例外「ParseException」が発生する興味深いパターン。SimpleDateFormatは実装の都合上、スレッドセーフではないため検査例外が必要なようです。
いずれにせよ、コンパイル時に「処理されない例外の型」などのメッセージが出た場合は、その部分をtry-catch文で囲むとエラーを解消できるでしょう。
Javaのtry-catch文による例外処理は、プログラムの規模が大きくなるほど重要性が増します。本章では、下記3つの応用テクニックをご紹介します。
前述したように、例外オブジェクトはクラスであり、catchする例外オブジェクトの大半は「Exception」のサブクラスです。つまり、「Exception」クラスを継承すれば、独自の例外オブジェクト・クラスを作れるということです。実際に下記のサンプルコードで、オリジナルの例外オブジェクトを作ってみましょう。
//サンプルプログラム |
class ZeroDivisionException extends Exception {
// コンストラクタ内部で例外メッセージを設定する ZeroDivisionException(String msg) { // スーパークラス(親クラス:Exception)のコンストラクタを呼び出す super(msg); } } public class Main { public static void main(String[] args) { try { // 割り算を行ってその答えを表示する System.out.println(“演算結果:” + divide(1, 0)); } catch (ZeroDivisionException e) { e.printStackTrace(); } finally { System.out.println(“プログラムを終了します”); } } public static int divide(int numA, int numB) throws ZeroDivisionException { // ゼロ除算の場合は例外を発生させる if (numB == 0) { // 「ZeroDivisionException」のインスタンスを生成することで、例外を投げることができる throw new ZeroDivisionException(“ゼロ除算が行われました”); } return numA / numB; } } |
//実行結果 |
ZeroDivisionException: ゼロ除算が行われました
at Main.divide(Main.java:32) at Main.main(Main.java:15) プログラムを終了します |
上記のサンプルプログラムでは、「ZeroDivisionException」という例外オブジェクトを作成しています。コンストラクタ内部では、親クラスの引数付きコントラクタを呼び出すために、「super」修飾子が必要です。divideメソッドで「ゼロ除算」が行われたとき、ZeroDivisionExceptionをthrowします。
Javaのtry-catch文では、ひとつのcatchブロックで複数の例外オブジェクトをcatchする、「マルチキャッチ」という機能が使えます。マルチキャッチするときは下記のように、例外オブジェクトを「|」で並べ、最後にひとつだけ変数を宣言すればOKです。
try {
例外が発生する可能性がある処理; } catch (例外1 | 例外2 | 例外3… 変数名) { 例外発生時のエラー処理; } |
マルチキャッチは、ひとつのメソッドが複数の例外を投げる可能性があるときに、catch節を複数書かなくて良いので便利です。実際に下記のサンプルコードで、マルチキャッチの書き方を確認しておきましょう。
//サンプルプログラム |
import java.io.FileNotFoundException;
public class Main { public static void main(String[] args) { // マルチキャッチで複数の例外オブジェクトをキャッチできるようにする try { // たくさんの例外を発生させるテストメソッドを呼び出す Test(); } catch (NullPointerException | ArrayIndexOutOfBoundsException | FileNotFoundException e) { // 例外メッセージを表示する e.printStackTrace(); } finally { System.out.println(“プログラムを終了します”); } } static void Test() throws NullPointerException, ArrayIndexOutOfBoundsException, FileNotFoundException { throw new FileNotFoundException(); } } |
//実行結果 |
java.io.FileNotFoundException
at Main.Test(Main.java:25) at Main.main(Main.java:10) プログラムを終了します |
上記のサンプルプログラムは、さまざまな例外を発生させる可能性がある「Testメソッド」を定義し、実際に例外を発生させます。メソッド呼び出し元ではマルチキャッチを使用しているので、Testメソッドでthrowする例外を変えてみて、どのように動作するかチェックしましょう。
なお例外はクラスなので、実際に受け取る例外オブジェクトの型は、「|」でつなげた例外オブジェクトすべてに共通するスーパークラスになります。たとえば、「NullPointException」や「ArrayIndexOUtOFBOUndsExeception」は「Runtime Exception」ですが、「IOException」や「ClassNotFoundException」は「Exception」となります。
そのため、例外オブジェクトに固有のメソッドがある場合は、下記のようなプログラムはコンパイルエラーとなるので注意してください。
//サンプルプログラム |
// テスト用の例外オブジェクトA
// Exceptionから派生し、固有の「method」メソッドを持つ class TestExceptionA extends Exception { void method() { } } // テスト用の例外オブジェクトB // Exceptionから派生し、固有の「method」メソッドを持つ class TestExceptionB extends Exception { void method() { } } public class Main { public static void main(String[] args) { try { // 複数の例外を発生させる可能性があるメソッドを呼び出す Test(); } catch (TestExceptionA | TestExceptionB e) { // 「e」は「Exception」として扱われるため、 // サブクラス固有のメソッドにアクセスする下記のような書き方は、コンパイルエラーとなる e.method(); // 下記のようにキャストすればOKだが、これなら最初からcatchブロックを分けたほうが良い if (e instanceof TestExceptionA) { ((TestExceptionA) e).method(); } else if (e instanceof TestExceptionA) { ((TestExceptionA) e).method(); } } } static void Test() throws TestExceptionA, TestExceptionB { throw new TestExceptionA(); } } |
上記のサンプルプログラムでは、独自の例外オブジェクト「TestExceptionA」と「TestExceptionB」を作成し、それをマルチキャッチで受けています。しかし、変数「e」はException型として扱われるため、サブクラス固有のメソッドにはアクセスできません。
「instanceof」演算子を使ってクラスの型名をチェックし、一致したサブクラスにキャストすれば固有のメソッドにアクセスできますが、推奨できない方法です。このような場合はそもそもマルチキャッチではなく、catch節を分けるほうが良いでしょう。
これまでのサンプルプログラムでは、「printStackTrace」メソッドで例外メッセージを表示してきました。しかし、ファイル名や行数などさまざまな情報が表示されるので、かえって例外の内容がわかりづらいこともあるでしょう。
そこで下記のサンプルコードのように、「getStackTrace」「getClass」「getName」などのメソッドを使えば、例外メッセージを簡略化できます。
//サンプルプログラム |
import java.io.BufferedReader;
import java.io.File; import java.io.FileReader; import java.io.IOException; public class Main { public static void main(String[] args) { // 「Test.txt」ファイルを開く File file = new File(“Test.txt”); // 「try-with-resources」を活用し、FileReaderとBufferedReaderのリソースが自動的に閉じられるようにする try (FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader)) { // テキストファイルの文字列を読み込んで、画面上に表示する String str; while ((str = bufferedReader.readLine()) != null) { System.out.println(str); } } catch (IOException e) { System.out.println(“例外が発生しました”); // スタックトレース(メソッド呼び出しの履歴)を取得する StackTraceElement[] stackTraceElement = e.getStackTrace(); // 例外の名称とメッセージをわかりやすく表示する System.out.println(“例外:” + e.getClass().getName()); System.out.println(“メッセージ:” + e.getMessage()); // 例外の発生場所を表示する(スタックトレースの末尾要素に情報がある) System.out.println(“例外発生場所:” + stackTraceElement[stackTraceElement.length – 1]); } finally { System.out.println(“プログラム終了!”); } } } |
//実行結果 |
例外が発生しました
例外:java.io.FileNotFoundException メッセージ:Test.txt (指定されたファイルが見つかりません。) 例外発生場所:Main.main(Main.java:13) プログラム終了! |
重要なポイントは、「StackTraceElement」オブジェクトなどを作成している部分です。StackTraceElementはメソッド呼び出しの履歴が格納されるオブジェクトで、最後の要素に例外が発生したメソッドの場所があります。
例外オブジェクトは、「getClass().getName()」で例外オブジェクトの名称、「getMessage」メソッドで例外メッセージを取得できます。以上のテクニックを活用すると、このように見やすい例外メッセージを表示でき、適切な例外処理が行いやすくなるでしょう。
Javaの「try-catch文」は、基本的には書かなくてもプログラムが成り立ちます。しかし、例外処理はプログラムのエラー処理を効率的に行うことができるため、うまく活用すればプログラムの安全性が飛躍的に高まる点は魅力です。とくに業務システムやアプリにおいては、try-catch文の知識は必要不可欠といっても過言ではありません。
また、try-catch文で発生する例外オブジェクトには、さまざまな型があるのでわかりづらいイメージがあるかもしれません。しかし、その大半が「Exception」のサブクラスであることや、よく見かける例外オブジェクトは限られていることなどを理解すると、すぐに慣れることができます。Javaのtry-catchを活用し、適切な例外処理を行いましょう。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール