楽ブログ

楽に楽しくやるブログ

スポンサーリンク(レスポンシブ)横長

スポンサーリンク

初心者入門レベルの【Javaの勉強】例外処理を使って異常終了しないプログラムを作る

2020/04/02

JAVAの勉強

今回は「例外処理」について、勉強しましょう。

プログラムを作るときに生じるエラーは、コンパイル時に分かるものであれば事前に修正することができます。

でも実行時に発生するものについては、エラーでプログラムが異常終了してしまってはユーザーにとって不便なので、その都度プログラムによって対応する必要があります。

このとき便利なのが、「例外処理」の仕組みです。

「例外処理」は必ずしもエラー対応のためにあるのではなく、単に処理の流れを通常とは違う方向に強制的に変更する仕組みなのですが、それが便利に使えるので主にエラー対応に利用されています。

実際の「例外処理」

さて、まずはサンプルとして以下のプログラムを作ってみましょう。

public class TestException01 {
  public static void main(String[] args) {
    int a = Integer.parseInt(args[0]);
    int b = Integer.parseInt(args[1]);
    int c = a / b;
    System.out.println(a + " / " + b + " = "  + c);
  }
}

このプログラムは、引数として渡した値を整数化して、1番目の引数を2番目の引数で割った商を求めています。

試しに“6”と“2”を引数として渡して実行してみると、次のような結果が得られます。

>java TestException01 6 2
6 / 2 = 3
 
>

では、このプログラムへ渡す2番目の引数を“0”にしてみるとどうなるでしょうか。

試しにやってみた結果、次のようなメッセージが表示されました。

>java TestException01 6 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at TestException01.main(TestException01.java:5)
 
>

これはプログラムを実行している途中で、「“/ by zero”(0で割った)という“Exception”(例外)が発生したため、処理を中断した」という意味を表すメッセージです。

このようにJavaの仮想マシンは、処理の途中で「例外」が発生すると、強制的に処理を中断してメッセージを表示するという仕組みになっています。

今回のプログラムのように小さなものならそれほど困りませんが、大きなシステムの場合、こういった「例外」が発生するたびに強制終了されてシステムが止まってしまうようでは、とても実用に耐えるものとは言えないでしょう。

そこで必要となるのが、「例外」の発生時にプログラム内で対応する仕組みなのですが、それが「例外処理」ということになります。

「例外処理」の記述は、基本的に以下のようになります。

try {
  「例外」が発生する可能性のある処理
}
catch(「例外」の種類 引数) {
  「例外」が発生したときの処理
}

ここで“try”に続く中カッコの部分を「tryブロック」と呼んで、“catch”に続く中カッコの部分を「catchブロック」と呼びます。

“catch”の引数として「例外」の種類が指定されてますが、この部分を変えてやることによって対応する「例外」の種類を変えることができます。

「例外」には上のプログラムでの実行例のように「0で割る」といったものの他にもいろいろなものがありますが、それぞれに対応した「catchブロック」を用意することで、各「例外」に対応するプログラムを作ることが可能です。

例として先程のプログラムについて、「0で割る」という「例外」に対応するように修正してみましょう。

public class TestException02 {
  public static void main(String[] args) {
    int a = Integer.parseInt(args[0]);
    int b = Integer.parseInt(args[1]);
    try {
      int c = a / b;
      System.out.println(a + " / " + b + " = "  + c);
      return;
    }
    catch(ArithmeticException e) {
      System.out.println("0で割ることはできません。");
    }
  }
}

これを実行してみると以下のようになって、0で割ったときの「例外」が発生したときの対応として「例外処理」が機能していることが確認できます。

>java TestException02 6 2
6 / 2 = 3
 
>java TestException02 6 0
0で割ることはできません。
 
>

これは「0で割る」という「例外」について対応しましたが、他にも“引数に数値ではなく文字を渡した場合”や、“2つ渡すべき引数が1つしかなかった場合”などにも「例外」が発生します

こういった「例外」に対応するように、さらに修正したのが以下のプログラムになります。

public class TestException03 {
  public static void main(String[] args) {
    try {
      int a = Integer.parseInt(args[0]);
      int b = Integer.parseInt(args[1]);
      int c = a / b;
      System.out.println(a + " / " + b + " = "  + c);
      return;
    }
    catch(ArithmeticException e) {
      System.out.println("0で割ることはできません。");
    }
    catch(NumberFormatException e) {
      System.out.println("数値を入力してください。");
    }
    catch(ArrayIndexOutOfBoundsException e) {
      System.out.println("引数を2つ入力してください。");
    }
  }
}

このように複数の「例外」には、それぞれの「例外」に応じた「catchブロック」を複数用意することによって対応することができます。

さらに「例外処理」には「finallyブロック」というものがあって、例えば以下のように記述します。

public class TestException04 {
  public static void main(String[] args) {
    try {
      int a = Integer.parseInt(args[0]);
      int b = Integer.parseInt(args[1]);
      int c = a / b;
      System.out.println(a + " / " + b + " = "  + c);
      return;
    }
    catch(ArithmeticException e) {
      System.out.println("0で割ることはできません。");
    }
    catch(NumberFormatException e) {
      System.out.println("数値を入力してください。");
    }
    catch(ArrayIndexOutOfBoundsException e) {
      System.out.println("引数を2つ入力してください。");
    }
    finally {
      System.out.println("プログラムを終了しました。");
    }
  }
}

「finallyブロック」は「例外」の発生とは無関係に必ず実行されるので、プログラムの終了時に必ず実行したい処理がある場合などに利用すると便利です。

「例外」の正体

これまで「例外」について実際のプログラムを使って見てきましたが、それではこの「例外」とは何かというと、その実体はJavaのクラスライブラリに用意された「クラス」です。

つまりJavaの仮想マシンは、「例外」が発生するとそれに該当する「例外」の「クラス」から「インスタンス」を生成し、その「インスタンス」の型に一致する「catchブロック」を見つけて実行するという処理を行っています。

ちなみにすべての「例外」の「クラス」は“Throwable”という「クラス」を「継承」していて、“Throwable”の下には“Exception”の他に“Error”という「クラス」もあります。

“Exception”の「クラス」は、プログラムを終了させることなく対応することができる状況のために用意されたものですが、“Error”のほうは通常はプログラムの続行が不可能なものなので特に処理を記述する必要は無いと言えるでしょう。

また、“Exception”が「例外」の「クラス」の「スーパークラス」であることを利用して、「例外処理」を以下のように記述したプログラムを作ることも可能です。

public class TestException05 {
  public static void main(String[] args) {
    try {
      int a = Integer.parseInt(args[0]);
      int b = Integer.parseInt(args[1]);
      int c = a / b;
      System.out.println(a + " / " + b + " = "  + c);
      return;
    }
    catch(Exception e) {
      System.out.println("例外が発生しました。");
    }
  }
}

こうすることで複数の「例外」について、その種類を指定することなく対応することができますが、ただし何の「例外」が発生したのか分からなくなってしまうので、利用する場合は注意が必要です。

独自の「例外」の作成

「例外」は、プログラムの記述によって意図的に発生させることも可能で、その場合は例えば以下のように記述します。

public class TestException06 {
  public static void main(String[] args) {
    throw new ArithmeticException();
  }
}

「例外」は「クラス」ですから、このように“new”によって「例外」の「インスタンス」を生成することができます。

次に“throw”という記述がありますが、これによって「例外」の「インスタンス」を処理に返すことで「例外」を発生しています。

これを「例外のスロー」とも呼びますが、上のプログラムはただ「例外のスロー」をしているだけなので、試しに実行してみると“ArithmeticException”のエラーメッセージが表示されます。

また、既存の「例外」の「クラス」を「継承」して、新しい「例外」を作ることも可能です。

それでは例として、以下のプログラムを作ってみましょう。

class MyException extends Exception {}
 
class Division {
  public void calc(int a, int b) throws MyException {
    if (b == 0) {
      MyException e = new MyException();
      throw e;
    }
    else {
      int c = a / b;
      System.out.println(a + " / " + b + " = "  + c);
      return;
    }
  }
}
 
public class TestException07 {
  public static void main(String[] args) {
    Division div = new Division();
    try {
      div.calc(6, 0);
      return;
    }
    catch(MyException e) {
      System.out.println("0で割ることはできません。");
    }
  }
}

プログラムの最初の1行目で、“Exception”の「クラス」を「継承」した「クラス」を作成しています。

この「クラス」の中身は空ですが、これを使って「例外のスロー」を行うことで処理を「catchブロック」へ移行できるので、「例外」の発生を捕らえるために十分に利用できます。

次に“Division”の「クラス」の中の“calc”という「メソッド」ですが、ここで“throws MyException”という記述があるのに注意してください。

このように「例外のスロー」をする「メソッド」は、多くの場合宣言に“throws”の記述が必要で、複数の「例外のスロー」をする場合は、それぞれの「例外」についてカンマで区切って記述します。

ただし、“RuntimeException”の「クラス」とその「サブクラス」については、この“throw”の記述は必要ありません。

さて、この“Division”の「クラス」は“main”の「メソッド」の中で「インスタンス化」され、続いて“calc”の「メソッド」が呼び出されています。

ここで注意が必要なのは、“throws”付きで宣言された「メソッド」を使う場合は、必ず「tryブロック」と「catchブロック」によって呼び出さなければならないということで、これらが無いとコンパイル時にエラーになります。

このプログラムを実行すると、“calc”の引数が“(6, 0)”になってますから、「例外」が発生して「catchブロック」のメッセージが表示されることを確認しましょう。

スポンサーリンク

まとめ

今回は「例外処理」について勉強しましたが、「例外」はJavaで用意されたライブラリの「クラス」の中の「メソッド」でも発生する場合があります。

ですからそういった「メソッド」を利用する際は、「例外」についても確認してプログラムで対応する必要がありますが、それについては公式ドキュメントに詳細が書かれているので調べてみましょう。

公式ドキュメントは、「Java(tm) Platform, Standard Edition 8 API仕様」(https://docs.oracle.com/javase/jp/8/docs/api/)として公開されています。

「例外処理」はユーザーに親切なプログラムを作るために欠かせない仕組みなので、しっかり活用できるように理解しておきましょう。

それでは、今回はここまでです。

《 【Javaの勉強】一覧 》

記事下アフィリウィジェット


こちらは、基礎からJavaを勉強したい人におすすめの書籍です。

スッキリわかるJava入門 第3版

著者:中山清喬、国本大悟

amazon

楽天ブックス

スッキリわかる Java入門 実践編 第2版

著者:中山清喬

amazon

楽天ブックス

スポンサーリンク

楽天モーションウィジェット(600×200)スマホ非表示

-PCやWEBの話
-,