第11章 例外処理(OCJP-Bronze範囲外)

はじめに

Javaではコンパイル時に発生するコンパイルエラーと実行時に発生する実行時エラーがあります。実行時に発生するエラーに対しては、処理が止まらないように対処できます。これが例外処理です。この章では、例外処理の仕組みと種類、そして例外処理の方法について学習していきます。

目次

  • 例外とは
  • try-catch-finally文
  • 例外クラス
  • 複数のcatch文を使用した処理
  • throws
  • try-finally文
  • throw
  • 章のまとめ

目標

例外処理について理解し、例外に対処するコーディングをできるようにすること

例外とは

例外とは、プログラム実行中に予定通り実行を継続できない状態が発生したことを言います。例えば、ユーザの入力に誤りがあった、間違ったファイルを実行しようとした、などの誤操作によるエラーはコンパイル時では対処できません。このようなエラーが発生したときに、それを検知し、対処する仕組みが提供されています。この仕組みを利用し、あらかじめ発生する可能性のある例外に対する処理を記述しておくことで、プログラムがそのエラーにより、終了してしまうことを避けることができます。

チェック例外と非チェック例外

例外は大きくチェック例外非チェック例外に分けることができます。

チェック例外

チェック例外とは、プログラムの論理ミスではなく、ファイル読み出しして指定したファイルが存在しないとか、通信先の相手が応答しないなどのプログラムの実行を継続できない状況が発生しうる可能性のある例外を指します。これらは、発生する場所がファイル入力メソッドや、通信メソッドなどのように特定できることができます。その為、例外が発生したかどうかチェックし、対処するコードコーディングする必要があります。対処するコードをコーディングしないとコンパイルエラーになります。

非チェック例外

非チェック例外とは、コンパイラが発見できないプログラムの論理ミスによって発生する例外です。発生する場所が予測できないので、通常は対処しません。例えば、ゼロによる除算や配列の範囲を超える要素アクセスや、nullによるオブジェクトの参照などが代表的な非チェック例外です。非チェック例外のことを実行時例外ともいいます。

例題

新たにExceptionExecクラスを作成し、実際に以下を入力してください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 ExceptionExec
package chapter11;
/**
* 実行するためのクラス
* @version 1.0
* @author Yamamoto
*/
public class ExceptionExec {

/**
* main()メソッド
*/
public static void main(String[] args) {
String[] str = {"A1","A2","A3","A4"};
display(str);
}

/**
* 表示機能
* @param str 出力文字列(配列)
*/
//配列の要素(5つ)をコンソールへ出力する
public static void display(String[] str){
for (int i = 0; i < 5 ; i++){
System.out.println(str[i]);
}
}
}

コンパイル例:

> javac chapter11¥ExceptionExec.java

実行例:

> java chapter11.ExceptionExec
A1
A2
A3
A4
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at exceptionPK.ExceptionExec.display(ExceptionExec.java:12)
at exceptionPK.ExceptionExec.main(ExceptionExec.java:7)

コンソールにjava.lang.ArrayIndexOutOfBoundsExceptionが発生したと表示されています。配列の範囲を超える要素が指定した為、例外が発生しています。

オブジェクト指向のJava言語では例外も例外クラスとして作成されています。いろいろな例外に対応するクラスがあらかじめ用意されており、例外が発生すると対応するクラスのオブジェクトが作成されます。ArrayIndexOutOfBoundsExceptionも例外クラスの一つで、java.langが先頭に付与されている為、java.langパッケージにあるクラスであることがわかります。

at alj_kensyu.ExceptionExec.display(ExceptionExec.java:12)
at alj_kensyu.ExceptionExec.main(ExceptionExec.java:7)

上記のat alj_kensyu.ExceptionExec.display(ExceptionExec.java:12)は例外の発生場所を示します。常に最上段の表示が発生場所を示します。これは次のように読み替えてください。

alj_kensyuパッケージの/ExceptionExecクラスの/displayメソッド/(ExceptionExec.javaファイルの12行目)

またat alj_kensyu.ExceptionExec.main(ExceptionExec.java:7)はdisplayメソッドを呼び出したメソッドを示します。

alj_kensyuパッケージの/ExceptionExecクラスの/mainメソッド/(ExceptionExec.javaファイルの7行目)

このように例外発生した行と、それを呼び出したメソッドの行が遡って表示されることにより、原因の特定が容易になります。メソッドの呼び出しの過程は、コールスタックというJVMが管理する記憶領域に記録されます。この仕組みを一般的にスタックトレースといいます。

try-catch-finally文

例外に対処するためには、try-catch-finally文を使います。

書式


try{

通常の処理(例外が発生すると思われる処理)

}catch(例外クラス 引数){

例外への対処

}finally{

最後に必ず実行する処理

}

先ほど作成したExceptionExecクラスを以下のように修正し、実行してみてください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 ExceptionExec

ExceptionExec.java

package chapter11;
/**
* 実行するためのクラス
* @version 1.0
* @author Yamamoto
*/
public class ExceptionExec {

/**
* main()メソッド
*/
public static void main(String[] args) {

String[] str = {"A1","A2","A3","A4"};
display(str);
}

/**
* 表示機能
* @param str 出力文字列(配列)
*/
public static void display(String[] str){
try{

for (int i = 0; i < 5 ; i++){
System.out.println(str[i]);
}

}catch(ArrayIndexOutOfBoundsException e){

//ArrayIndexOutOfBoundsExceptionが発生したときの例外処理
System.out.println("catchブロック実行しました。");

}finally{

System.out.println("finallyブロック実行しました。");

}
}
}

コンパイル例:

> javac chapter11¥ExceptionExec.java

実行結果:

> java chapter11.ExceptionExec
A1
A2
A3
A4
catchブロック実行しました。
finallyブロック実行しました。

tryブロック

displayメソッドにtry-catch-finally文が書いてあり、例外が発生する可能性のあるコードはtryブロックの中に書きます。

try{

for (int i = 0; i < 5 ; i++){
System.out.println(str[i]); //例外が発生する可能性のある部分
}

}

catchブロック

例外が発生すると、それ以降の処理を中止して、直ちにcatchブロックに制御が移ります。catchの直後のカッコ内「()」に例外クラスを指定します。以下の場合、ArrayIndexOutOfBoundsExceptionが発生すると引数eに例外の詳細情報がセットされます。

catch(ArrayIndexOutOfBoundsException e){

System.out.println("catchブロック実行しました。");

}

finallyブロック

例外発生の有無にかかわらず、try-catchブロックの最後に必ず実行する処理を書きます。

finally{

System.out.println("finallyブロック実行しました。");

}

なお、必要のない場合にはfinallyブロックを省略することができます。

例外クラス

オブジェクト指向のJava言語では、例外をそれぞれ一つのクラスで表します。例外クラスの継承関係を以下に示します。

例外クラス

例外クラスのスーパークラスはExceptionです。サブクラスのRuntimeExceptionは、全て非チェック例外(実行時例外)のスーパクラスです。またIOExceptionは入出力関連の例外のスーパークラスです。

主要な実行時例外(非チェック例外)は以下です。

実行時例外(非チェック例外)

体験的に理解する方がしっかり覚えることができますので、以下コーディングし、実行結果を確認してみてください。

ClassCastException

例外クラス名 意味
ClassCastException キャストの失敗
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 ClassCastExceptionExec
package chapter11;
public class ClassCastExceptionExec {

public static void main(String[] args) {
Animal a = new Animal();
Cat b = (Cat)a; //ダウンキャスト

}
}

class Animal {}

class Cat extends Animal{
}

コンパイル例:

> javac chapter11¥ClassCastExceptionExec.java

実行結果:

> java chapter11.ClassCastExceptionExec
Exception in thread "main" java.lang.ClassCastException: class chapter11.Animal cannot be cast to class chapter11.Cat (chapter11.Animal and chapter11.Cat are in unnamed module of loader 'app')
at chapter11.ClassCastExceptionExec.main(ClassCastExceptionExec.java:6)

NumberFormatException

例外クラス名 意味
NumberFormatException 数値形式文字列の書き誤り
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 NumberFormatExceptionExec
package chapter11;

public class NumberFormatExceptionExec {

public static void main(String[] args) {

int a = Integer.parseInt("1.0");
int c = Integer.parseInt("aaa"); //数値に直せない文字列
}

}

コンパイル例:

> javac chapter11¥NumberFormatExceptionExec.java

実行結果:

> java chapter11.NumberFormatExceptionExec
Exception in thread "main" java.lang.NumberFormatException: For input string: "1.0"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatExceptionExec.java:68)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at chapter11.NumberFormatException.main(NumberFormatExceptionExec.java:7)

IllegalStateException

例外クラス名 意味
IllegalStateException 不正な状態での呼び出し
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 IllegalStateExceptionExec
package chapter11;

import java.util.Scanner;

public class IllegalStateExceptionExec {

public static void main(String[] args) {
Scanner s = new Scanner(System.in);
s.close(); //クローズ処理
String str = s.next(); //クローズ後の利用

}

}

コンパイル例:

> javac chapter11¥IllegalStateExceptionExec.java

実行結果:

> java chapter11.IllegalStateExceptionExec
Exception in thread "main" java.lang.IllegalStateException: Scanner closed
at java.base/java.util.Scanner.ensureOpen(Scanner.java:1150)
at java.base/java.util.Scanner.next(Scanner.java:1465)
at chapter11.IllegalStateExceptionExec.main(IllegalStateExceptionExec.java:10)

IndexOutOfBoundsException

例外クラス名 意味
IndexOutOfBoundsException 不正な状態での呼び出し(配列や文字列操作で範囲を超えたとき)
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 IndexOutOfBoundsExceptionExec
package chapter11;

public class IndexOutOfBoundsExceptionExec {

public static void main(String[] args) {

String str = "asdfg";
char c = str.charAt(6); //5までしかない
}

}

コンパイル例:

> javac chapter11¥IndexOutOfBoundsExceptionExec.java

実行結果:

> java chapter11.IndexOutOfBoundsExceptionExec
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6
at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
at java.base/java.lang.String.charAt(String.java:711)
at chapter11.IndexOutOfBoundsExceptionExec.main(IndexOutOfBoundsExceptionExec.java:8)

ArrayIndexOutOfBoundsException

例外クラス名 意味
ArrayIndexOutOfBoundsException 配列の要素番号が範囲外
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 ArrayIndexOutOfBoundsExceptionExec
package chapter11;

public class ArrayIndexOutOfBoundsExceptionExec {

public static void main(String[] args) {
int[] a = {1,2,3};
System.out.println(a[5]);
}

}

コンパイル例:

> javac chapter11¥ArrayIndexOutOfBoundsExceptionExec.java

実行結果:

> java chapter11.ArrayIndexOutOfBoundsExceptionExec
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
at chapter11.ArrayIndexOutOfBoundsExceptionExec.main(ArrayIndexOutOfBoundsExceptionExec.java:7)

NullPointerException

例外クラス名 意味
NullPointerException 使ったオブジェクトがNull
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 NullPointerExceptionExec

NullPointerExceptionExec.java

package chapter11;

public class NullPointerExceptionExec {

public static void main(String[] args) {
String str = null;
System.out.println(str.equals("a"));
}

}

コンパイル例:

> javac chapter11¥NullPointerExceptionExec.java

実行結果:

> java chapter11.NullPointerExceptionExec
Exception in thread "main" java.lang.NullPointerException
at chapter11.NullPointerExceptionExec.main(NullPointerExceptionExec.java:7)

ArithmeticException

例外クラス名 意味
ArithmeticException 算術エラー
項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 ArithmeticExceptionExec
package chapter11;

public class ArithmeticExceptionExec {

public static void main(String[] args) {
int a = 50 / 0;

}

}

コンパイル例:

> javac chapter11¥ArithmeticExceptionExec.java

実行結果:

> java chapter11.ArithmeticExceptionExec
Exception in thread "main" java.lang.ArithmeticException: / by zero
at chapter11.ArithmeticExceptionExec.main(ArithmeticExceptionExec.java:6)

複数のcatch文を使用した処理

ある処理が、種類の違う複数の例外を発生する場合があります。例えば、ファイルからデータを読み出す処理では、指定されたファイルが見つからない、ファイルが破損していて読み出せないなど、複数の例外が発生する可能性があります。このような場合はtry-catch-finally文の中でcatchブロックを複数定義して対応します。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 FileReaderExec
package chapter11;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderExec {

public static void main(String[] args) {
try {
FileReader in = new FileReader("test.txt");
int c = in.read();
System.out.println((char)c);
in.close();
} catch (FileNotFoundException e) {
System.err.println("ファイルが見つかりません。");
} catch (IOException e) {
System.err.println("ファイルを読めません");
}
}

}

コンパイル例:

> javac chapter11¥FileReaderExec.java

実行結果:

> java chapter11.FileReaderExec
ファイルが見つかりません。

上記、例外が発生すると、catch文が上から順に検査され、最初に一致したcatch文だけが実行されます。最初のcatch文で、FileNotFoundExceptionを指定しています。2つ目のcatch文でIOExceptionを指定しています。

なお、IOExceptionFileNotFoundExceptionのスーパークラスです。catchブロックで複数の例外を指定する場合は、サブクラスを先に指定し、最後にスーパークラスを指定することに注意してください。この順序を守らないとコンパイルエラーになります。

throws

Java言語では例外が発生したメソッドで例外処理を行わないと、例外は呼び出し元のメソッドへ伝達される仕組みになっています。発生した例外がどこかで対応する例外処理に遭遇するまで、メソッド呼び出し経路(コールスタック)を遡って伝達されます。

例外がこのように伝達されることを利用すると、責任を放棄して呼び出し元メソッドに任せることも可能です。それにはメソッドの宣言部でthrowsキーワードを使って処理を放棄する宣言をします。これを例外を交わすといいます。

先ほど作成したFileReaderExecクラスを以下のように修正及び追加し、実行してみてください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter11
クラス名 FileReaderExec
package chapter11;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderExec {

public static void main(String[] args) {
try {
//-----ここから(修正)-----
readFile();
//-----ここまで(修正)-----
} catch (FileNotFoundException e) {
System.err.println("ファイルが見つかりません。");
} catch (IOException e) {
System.err.println("ファイルを読めません");
}
}

//-----ここから(追加)-----
public static void readFile() throws IOException {

FileReader in = new FileReader("test.txt");
int c = in.read();
System.out.println((char)c);
in.close();
}
//-----ここまで(追加)-----
}
```

コンパイル例:

javac chapter11¥FileReaderExec.java


実行結果:

java chapter11.FileReaderExec
ファイルが見つかりません。


※`System.err.println`は標準エラー出力といいます。標準エラー出力はエラー情報の出力するときに使います。

## try-finally文

例外に関係なく実行したい処理があるとき、try-finally構文を使います。

| 項目 | 名前 | 備考 |
|:------------|:----------|:----------|
| プロジェクト | alj_study | |
| パッケージ | chapter11 | |
| クラス名 | FileReaderExec2 | |

package chapter11;

import java.io.FileReader;
import java.io.IOException;

public class FileReaderExec2 {

public static void main(String[] args) throws Exception{

    FileReader in = null;   //nullで初期化しておく。

    try {

        in = new FileReader("test.txt");
        int c = in.read();
        System.out.println((char)c);

    } finally{

        System.out.println("finallyブロック実行しました。");

    }
}

}


コンパイル例:

javac chapter11¥FileReaderExec2.java


実行結果:

java chapter11.FileReaderExec2
finallyブロック実行しました。
Exception in thread “main” java.io.FileNotFoundException: test.txt (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:212)
at java.base/java.io.FileInputStream.(FileInputStream.java:154)
at java.base/java.io.FileInputStream.(FileInputStream.java:109)
at java.base/java.io.FileReader.(FileReader.java:60)
at chapter11.FileReaderExec2.main(FileReaderExec2.java:14)


※finallyブロックは例外が発生しても、しなくてもtryの中の処理に続いて必ず実行されます。


## throw

メソッドでは、渡された引数の値が不正だったり、読み込んだデータが異常だった場合など、処理を中止せざるをえなくなる場合があります。そんなときに強制的に例外を起こすことによって、異常事態の発生を呼び出し側メソッドに伝えることができます。例外を起こすには`throw文`を使います。`throw文`は`強制的に例外を発生させる`ことができる例外文です。

構文

throw 例外クラスオブジェクトの参照 ;


`throw文`は一般的に、`「例外を投げる」`といいます。


| 項目 | 名前 | 備考 |
|:------------|:----------|:----------|
| プロジェクト | alj_study | |
| パッケージ | chapter11 | |
| クラス名 | ThrowExec | |

package chapter11;

public class ThrowExec {

public static void main(String[] args) { 
    try {
        display(args);
    } catch (Exception e) {
        System.err.println(e.getMessage());
    }
}

public static void display(String[] n) throws Exception{

    if(n.length == 0) {
        throw new Exception("引数が指定されていない");     //例外を投げる
    }else{
        for(String str : n ){
            System.out.println(str);
        }       
    }

}

}


コンパイル例:

javac chapter11¥ThrowExec.java


実行結果(引数指定無し):

java chapter11.ThrowExec
引数が指定されていない


実行結果(引数指定有り):

java chapter11.ThrowExec 123 987 234
123
987
234


上記は、display()メソッドでは、引数で指定された配列の要素がない場合は、
例外を投げる処理(例外を自ら発生させる処理)を定義しています。

if(n.length == 0) {
    throw new Exception("引数が指定されていない");     //例外を投げる
}else{
    for(String str : n ){
        System.out.println(str);
    }       
}

```

Exceptionはチェック例外の為、例外処理であるtry-catch文を定義するか、 throwsの宣言が必須です。
今回はdisplay()メソッドでthrows Exceptionを宣言し、呼び出し元であるmain()メソッドで、例外処理であるtry-catch文を定義することで例外を対処しています。

今回は、ExceptionクラスのメソッドgetMessage()を利用し、メッセージを表示させています。

例外クラスのメソッド

投げられた例外を処理する側のメソッドでは、例外クラスのメソッドを利用して例外処理を行うことができます。

例外クラスのスーパクラスであるExceptionクラスには、色々なメソッドがあります。

メソッド名 内容
printStackTrace() スタックトレースを表示する
toString() 例外の種類とメッセージ文字列を返す
getMessage() メッセージ文字列を返す

使い方については、以下リファレンスをご参考ください。

「printStackTrace()」メソッド

「toString() 」メソッド

「getMessage() 」メソッド

章のまとめ

以下の要点をしっかり理解してから次の章へ進んでください。

  • 例外にはチェック例外非チェック例外がある。
  • 例外に対処するには例外の発生しそうなコードをtry-catchブロックで囲む。
  • catch文の引数には例外に対応する例外クラスを指定する。
  • tryブロックで例外が発生すると直ちにcatchブロックに制御が移る。
  • 複数のcatch文を書くことができるが、実行されるのは一つだけである。
  • finallyブロックはtry-catchブロックの実行後、必ず実行される。
  • 例外クラスのスーパークラスはThrowableクラスである。
  • ThrowableクラスからErrorクラスExceptionクラスが派生する。
  • ExceptionクラスからはRuntimeExceptionIOExceptionなどが派生する。
  • RuntimeExceptionクラスは実行時例外(非チェック例外)のスーパークラスである。
  • catch文が複数あるときは、サブクラスを補足するcatch文先に書く
  • 例外処理を行わないと、次々に呼び出し元のメソッドに例外が伝達される。
  • チェック例外はtry-catchブロックで必ず例外処理を行わなければならないが、throwsキーワードで例外処理を呼び出し元へ、任せることもできる。
  • 例外を投げるにはthrow文を使う。
  • チェック例外を投げる場合は、メソッドでthrows宣言が必要である。