📚

例外処理の基本<前編>[Java入門]

に公開

はじめに

こんにちは。
プログラミング初心者Wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。

十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。
間違い等あれば、指摘いただけると助かります。

対象読者

  • Javaを勉強中の方
  • Java Silver試験を勉強中の方
  • Javaの例外について知りたい方

目次

1. 不具合と例外
2. 例外インスタンスのメソッド
3. try-catch-finally

本文

1. 不具合と例外

プログラムが想定通りに動かない事態を「不具合」と言います。不具合は、主に3つに分けられます。

  • 文法エラー:文法の誤りのため、コンパイルに失敗すること。例としては、セミコロン(;)の記述忘れ、変数名の間違いなどがある
  • 実行時エラー:コンパイルは成功するが、実行時に何らかの異常事態が発生して、動作が継続できなくなること。例としては、配列の範囲外要素へのアクセス、0での割り算などがある
  • 論理エラー:コンパイルに成功し、プログラムも強制終了しないが、実行結果が想定していた内容と異なること。例として、計算プログラムなどで計算結果が間違っているなどがある

このうち文法エラーと論理エラーは「開発者の過失」が原因です。そのため、開発者が事前にテストを行い、コードを修正しておけば、エラーの発生を予防できます。

しかし、実行時エラーの多くは、想定外の事態によって起こります。この想定外の事態を、「例外的状況」もしくは「例外」と言います。
想定外の事態の発生を完全に予防することは困難であるため、例外が発生した場合の代替案を用意することで対応します。
このように、例外に備えて、例外的状況に陥った時の対策を実施することを「例外処理」と言います。

1口で「例外」と言っても、その状況はさまざまです。 そのため、Javaでは、標準APIからさまざまな例外的状況を表す「例外クラス」が複数提供されています。

Throwable
├── Error
└── Exception             
    ├── RuntimeException  
    └── (その他)

Javaの例外クラスの階層構造は、大まかに上図のようになります。
最上位に位置するのがjava.lang.Throwableで、あらゆる例外クラスの親になります。

例外クラスは以下のように分類されます。

1,Error系
2,Exception系
2.1 RuntimeException以外のException系 (チェック例外)
2.2 RuntimeException系 (非チェック例外)

  • Error系例外:java.lang.Errorの子孫で、回復の見込みがない致命的な状況を表すクラス。例としては、OutOfMemoryError(メモリ不足),ClassFormatError(クラスファイルが壊れている)がある。このような状況をcatchしても打つ手はないため、通常は例外処理を行いません

  • Exception系(RuntimeException以外):java.lang.Exceptionの子孫のうちRuntimeExceptionの子孫ではないものを指す。その例外の発生を想定して対処を考える必要がある状況を表すクラス。「チェック例外」、「検査例外」ともいう。例としては、IOException(ファイルが読み書きできない)、ConnectException(ネットワークに接続できない)などがある。
    例外が出る可能性のある処理をしながら例外処理を記述しないと、コンパイラエラーになります。その理由は、開発者に例外処理を疎かにすると、プログラムが脆弱になってしまうためです。例外処理を強制することで、より堅牢なプログラムを実現しているのです

  • RuntimeException系:java.lang.RuntimeExceptionの子孫のこと。Exceptionのうち、例外処理を強制されない例外がここに分類される。例としては、NullPointerException(変数がnullである)、ArrayIndexOutOfBoundsException(配列のインデクスが不正)などがある。例外処理を行うかどうかは任意である

あるメソッドを呼び出した時に、どのような例外を発生させる可能性があるかは、APIリファレンスで確認することができます。

2. 例外インスタンスのメソッド

例外クラスの最上位に位置するjava.lang.Throwableには、例外処理に必要なメソッドが定義されています。すべての例外クラスはThrowableを継承しているため、これらのメソッドを利用することができます。
代表的なメソッドは以下の2つです。

  • public String getMessage()
    例外的状況の解説文(エラーメッセージ)を取得する

  • public void printStackTrace()
    スタックトレースの内容を画面に出力する

3. try-catch-finally

次に具体的な例外処理の手順を説明していきます。
まずは、try-catch-finally構文です。

try{
   例外が起こるかもしれない処理
} catch (例外クラス 変数名) {
   例外が発生した場合の処理
} finally {
   例外が起きても起きなくても必ず実行したい処理
}

まず、tryブロック内に例外が起こるかもしれない処理を記述します。
次に、catchブロックの()内に起こるかもしれない例外クラスを記述し、{}内に例外が起こった場合の処理内容を記述します。
これにより、tryブロック内で例外が発生した場合、自動的に該当する例外クラスを指定したcatchブロックに処理が移行し、ブロック内の例外処理が実行されます。
tryブロックで例外が発生しない場合は、catchブロックの処理は行われません。

finallyブロックは、例外が起きても起きなくても必ず行いたい処理を記述します。tryブロックの途中で例外が起こった場合は、tryブロック内の処理はその時点で中断されます。つまり、必ず行いたい処理をtryブロックに記述しても、実行されない場合があるのです。tryブロックで例外が発生しない場合は、catchブロックの処理は行われないため、必要な処理をcatchブロックに書くこともできません。
そのため、必ず行わなければならない処理は、tryブロックでもcatchブロックでもなく、finallyブロックに記述します。finallyブロックは、例外が発生した場合はcatchブロックの後に、例外が発生しなかった場合はtryブロックの後に必ず実行されます。
ただ、ファイル読み書きのclose()メソッドなどの処理は、現在はfinallyに記述するより、try-with-resources文での記述が一般的です。try-with-resourcesは次回の記事で紹介します。

catchブロックは必要に応じて、複数記述することができます。
また、finallyブロックは、必要がない場合省略できます。

具体的なコードで見ていきましょう

public class Main {
   public static void main(String[] args){
      int[] num = {10,20,30};
      for (int i = 0; i < 4; i++){
         try {  //例外が起こるかもしれない処理を記述
            System.out.println("num : " + num[i]); //ここで例外が起きる
            System.out.println(i + "回目のループ"); //例外発生後、この処理は行われない
         } catch (ArrayIndexOutOfBoundsException e){ //例外処理を記述
            System.out.println("例外が発生しました");
            System.out.println("配列のインデクスが不正です");
            System.out.println(e.getMessage());
         } finally { //必ず行いたい処理を記述
            System.out.println("finallyの実行");
         }
      }
   }
}

上のコードでは、tryブロックのSystem.out.println("num : " + num[i]);の部分でArrayIndexOutOfBoundsExceptionが発生します。例外が発生した時点で、tryブロック内の以後の処理は中断され、ArrayIndexOutOfBoundsExceptionを指定しているcatchブロックに移行します。catchブロックの処理が完了すれば、finallyブロックの処理に移行するという流れです。

ちなみに、catch (ArrayIndexOutOfBoundsException 「e」)の「e」は、例外クラスのインスタンスへの参照を格納する変数です。JVMが例外をキャッチすると、「プログラムの中のどこで、どのような例外が起こったのか」という例外的状況の詳細情報を格納した例外インスタンスが生成されます。このインスタンスへの参照は、catchブロックで指定された変数eに格納されます。
変数eに格納された情報は、getMessage()やprintStackTrace()などのメソッドを利用して取り出すことができます。(例:e.getMessage())

例外が発生しても例外処理がないと、そのままプログラムは強制終了してしまいます。しかし、適切な例外処理を記述することで、例外的状況の詳細を出力したり、プログラムのリカバリーを行うことができるのです。

まとめ

  • プログラムの不具合は「文法エラー」「実行時エラー」「論理エラー」に分けられ、このうち「実行時エラー」が例外とよばれる想定外の事態である
  • Javaの例外クラスは「Error系」「RuntimeException以外のException系(チェック例外)」「RuntimeException系(非チェック例外)」に分類される
  • 例外処理の基本パターンは、try-catch-finally構文である。
  • getMessage()やprintStackTrace()といったメソッドで、例外の詳細情報を取得することができす¥る。

前篇の記事は以上です。

次回の後編で、try-with-resources、throw、throws、例外処理のポリモーフィズムについてまとめる予定です。
最後までお読みいただき、ありがとうございました。

参考情報一覧

この記事は以下の情報を参考にして執筆しました。

GitHubで編集を提案

Discussion