[Java] 16. 例外処理(try~catch~finally, throw)を使う方法


Study / Java    作成日付 : 2019/08/26 23:40:29   修正日付 : 2021/01/20 15:05:32

こんにちは。明月です。


この投稿はJavaの例外処理(try~catch~finally, throw)を使う方法に関する説明です。


プログラムを作成しながら、予想できなかった例外が発生する時があります。この例外がプログラムを間違って実装して発生するバグもありますが、プログラムを使っているユーザからも間違ってデータを入力や既存のデータがnullの場合も発生する時があります。

特にプログラムの運用でよく発生する例外はnull exceptionです。

// クラス
public class Example {
  // 関数
  public void run() {
    // コンソール出力
    System.out.println("run!!");
  }
  // 実行関数
  public static void main(String... args) {
    // 変数タイプをExampleで宣言インスタンスをまだ生成しない
    Example ex = null;
    // Exampleクラスのrun関数を呼び出す。
    ex.run();
    // コンソール出力
    System.out.println("hello world");
  }
}


上の例をみればex変数はインスタンスを生成しない、つまり割り当てしなかったので、ex.run()を呼び出すとheapメモリに生成されてないクラスから関数を探すのでエラーが発生します。

ここでエラーが発生するとエラーを無視して次のステップに実行することではなく、プログラムがそのまま終了になります。上の例をみればex.runでエラーが発生してhello worldというメッセージはコンソールに表示されてないです。

そのため例外処理をすることです。使用方法には何処にエラーが発生する可能性がある部分でtryキーワードと中括弧でStack領域を設定して例外が発生すればcatchに移動することに設定します。

// クラス
public class Example {
  // 関数
  public void run() {
    // コンソール出力
    System.out.println("run!!");
  }
  // 実行関数
  public static void main(String... args) {
    // 例外処理
    // tryスタック領域でエラーが発生すればcatchに移動する。
    try {
      // 変数タイプをExampleで宣言インスタンスをまだ生成しない
      Example ex = null;
      // Exampleクラスのrun関数を呼び出す。
      ex.run();
      // コンソール出力
      System.out.println("not error");
    } catch (Throwable e) {
      // コンソールにエラー出力
      System.out.println(e);
    }
    // コンソール出力
    System.out.println("hello world");
  }
}


上の例を見ればex.run関数でエラーが発生したので、catchのエラー内容がコンソールに出力しました。次のhello worldがコンソール出力しました。not errorというメッセージは同じtryスタック領域にあるのでエラーが発生することで飛ばしました。

ここで私がcatchの中でThrowableというインタフェースを使いました。catchには条件式にエラークラスやインタフェースが必要ですが、Throwableインタフェースはすべてのエラークラスの最上位のインタフェースです。

この最上位インタフェースを設定すればすべてのエラーがcatch(Throwable)に移動します。


次の例で説明します。

//クラス
public class Example {
  // 関数
  public void run() {
    // コンソール出力
    System.out.println("run!!");
  }
  // 実行関数
  public static void main(String... args) {
    try {
      // 変数タイプをExampleで宣言インスタンスをまだ生成しない
      Example ex = null;
      // Exampleクラスのrun関数を呼び出す。
      ex.run();
      // コンソール出力
      System.out.println("not error");
    // NullPointerExceptionだけを移動する。
    } catch(NullPointerException e) {
      // エラー内容をコンソールに出力
      System.out.println("null exception");
    // すべてのエラーを移動する。
    } catch (Throwable e) {
      // エラー内容をコンソールに出力
      System.out.println(e);
    }
    // コンソール出力
    System.out.println("hello world");
  }
}


上の例はcatchが二つがあります。ex.run()にはNullPointerExceptionが発生(参考-最初の例をみれば結果にNullPointerExceptionが発生しましたと赤い文字で表示しました。)したのでcatch(NullPointerException)の領域に移動しました。

もしかして、NullPointerExceptionではない場合はcatch (Throwable)に移動することです。

// クラス
public class Example {
  // 関数
  public void run() {
    // コンソール出力
    System.out.println("run!!");
  }
  // 実行関数
  public static void main(String... args) {
    try {
      // 10を0で割ってエラーを発生する。
      int a = 10 / 0;
      // コンソール出力
      System.out.println("not error");
    // NullPointerExceptionだけを移動する。
    } catch(NullPointerException e) {
      // エラー内容をコンソールに出力
      System.out.println("null exception");
    // すべてのエラーを移動する。
    } catch (Throwable e) {
      // エラー内容をコンソールに出力
      System.out.println(e);
    }
    // コンソール出力
    System.out.println("hello world");
  }
}


上の例をみれば10を0で割ればArithmeticExceptionエラーが発生してcatch(Throwable)の領域に移動しました。

このcatch機能によって分けている理由は仕様によってエラー別で処理を別に処理することができるのでです。ここで注意事項はcatchの順番です。

もしかして、NullPointerExceptionとThrowableの順番を変わるとコンパイルエラーが発生します。


今回は関数の中で例外処理をしましょう。

// クラス
public class Example {
  // メンバー変数
  private int data;
  // コンストラクタ
  public Example(int data) {
    // メンバー変数設定
    this.data = data;
  }
  // 計算関数
  public int calc() {
    try {
      // 100でメンバー変数dataで値を割る
      return 100 / this.data;
    // ArithmeticExceptionの例外処理
    } catch (ArithmeticException e) {
      // エラーが発生すれば0をリターン
      return 0;
    }
  }
  // 実行関数
  public static void main(String... args) {
    // インスタンスを生成
    Example ex = new Example(0);
    // 結果をコンソールに出力
    System.out.println("result - " + ex.calc());
    // 結果をコンソールに出力
    System.out.println("result - " + ex.calc());
  }
}


今回はcalc関数でメンバー変数dataが0になるとArithmeticExceptionエラーが発生して結果を0をリターンする。

でも、私はtry~catchに関係ずにtry領域が終わったら必ず実行したいステップがある可能性があります。例えば,メンバー変数dataを1に設定することかです。

それならreturnする前にメンバー変数dataを1に更新すればよいですが、もっとプログラム品格でfinallyを使えます。

// クラス
public class Example {
  // メンバー変数
  private int data;
  // コンストラクタ
  public Example(int data) {
    // メンバー変数設定
    this.data = data;
  }
  // 計算関数
  public int calc() {
    try {
      // 100でメンバー変数dataで値を割る
      return 100 / this.data;
    } catch (ArithmeticException e) {
      // エラーが発生すれば0をリターン
      return 0;
    } finally {
      // tryが終わると必ずメンバー変数dataを1に格納
      this.data = 1;
    }
  }
  // 実行関数
  public static void main(String... args) {
    // インスタンスを生成
    Example ex = new Example(0);
    // 結果をコンソールに出力
    System.out.println("result - " + ex.calc());
    // 結果をコンソールに出力
    System.out.println("result - " + ex.calc());
  }
}


finally領域は関数でもうreturnを実行しても関数が終わる前にfinally領域を実行します。

特にfinallyはIOやソケットなどの外部リソースを使う時にリソースリターン式でよく使います。finallyはcatchがなくても使えます。つまり、try~finally文法もできるという意味です。


エラークラス(Exception)は基本的にJavaでたくさんあります。それでも、私がエラークラス(Exception)を作りたいです。エラークラス(Exception)を作ることは他のオブジェクトを参照する時にエラーを発生してクラスを信頼性を上がることです。

それならこのExceptionクラスを作ることはThrowableインタフェースを継承すればいいと思いますが、JavaではThrowableインタフェースを継承ができなくて、ExceptionとRuntimeExceptionを継承しなければならないです。

もちろん、その二つを継承したクラスを継承することもできます。

// TestExceptionを生成した
class TestException extends RuntimeException {
  // 実はコンストラクタや関数の再定義でエラークラスの特性をもっと作られます。
}
// クラス
public class Example {
  // 関数
  public static void test() {
    // throwを通ってエラーが発生
    throw new TestException();
  }
  // 実行関数
  public static void main(String... args) {
    // test関数を呼び出す。
    test();
  }
}


test関数を呼び出すと中でthrowのキーワードによってエラーが発生しますが、コンソールを見ればステップ追跡も表示されます。


ここでthrowのキーワードを使う時に明示的エラー処理と暗黙的のエラー処理があります。

明示的エラー処理はコンパイル段階でエラーが発生する可能性がありますような表示することです。関数のパラメータの右でthrowsのキーワードを使います。

// クラス
public class Example {
  // 関数
  public static void test() {
    // Exception発生
    throw new Exception();
  }
  // 実行関数
  public static void main(String... args) {
    // 関数呼び出す。
    test();
  }
}


上の例をみればthrow new Exceptionを使いましたが、eclipseからエラーを制御してないというエラーが発生しました。つまり、コンパイルからエラーが発生します。

// クラス
public class Example {
  // 関数の右でthrowsキーワードで何のエラーが発生するか明示的に表示
  public static void test() throws Exception {
    // Exception発生
    throw new Exception();
  }
  // 実行関数
  public static void main(String... args) {
    // 関数呼び出す。
    test();
  }
}


そのため、test関数を呼び出す時に別にException例外処理をしてないならエラーが発生します。

// クラス
public class Example {
  // 関数の右でthrowsキーワードで何のエラーが発生するか明示的に表示
  public static void test() throws Exception{
    // Exception発生
    throw new Exception();
  }
  // 実行関数
  public static void main(String... args) {
    // Exceptionの例外処理する。
    try {
      test();
    } catch (Exception e) {
      // エラー内容を表示する。
      // printStackTrace関数をすればcall stackまですべて表示する。(よく使う方法)
      e.printStackTrace();
    }
  }
}


今回は実行できます。

暗黙的のエラー処理はthrowsを使わないことですが、RuntimeExceptionを継承します。

// クラス
public class Example {
  // エラーを発生するが、コンパイルで問題ない。
  public static void test(){
    throw new RuntimeException();
  }
  // 実行関数
  public static void main(String... args) {
    // 関数呼び出す。
    test();
  }
}


暗黙的のエラー処理だってtry~catchに取れないことではありません。Throwableでcatchを作ればすべてのエラーが取れます。


ここでExceptionクラスとRuntimeExceptionクラスの差異はExceptionを継承してエラークラスを作ればすべて明示的にエラー処理になるし、RuntimeExceptionを継すれば暗黙的のエラー処理になります。

参考にエラークラスの階層はThrowable - Exception - RuntimeExceptionです。すなわち、try~catchでExceptionをRuntimeExceptionより上の階層なので、Exception領域でもすべてのエラーを取れます。


ここまでJavaの例外処理(try~catch~finally, throw)を使う方法に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿