[Java] 例外, アサーション のメモ

わかりやすいJava_オブジェクト指向編を読んで勉強中。。

参考: Javaの道:例外

例外の種類

例外はチェック例外と非チェック例外(実行時例外: RuntimeException)の2種類に分けられる。チェック例外は発生源が特定できる例外で、必ずキャッチできるようにコーディングしなければコンパイルエラーとなる。非チェック例外はコンパイラでは発見できないプログラムの論理ミスによって発生する。(JVMのメモリ不足などの致命的な欠陥は例外ではなくエラー)

try-catch-finally

cache の丸括弧で指定する例外に Exception は使うべきではない。 NullPointerException だとか具体的な例外をキャッチすること。finally ブロックは例外の発生有無にかかわらず必ず実行される

finally 文が実行されるタイミング

package test;

public class Test {

  public static void main(String[] args) {
    test("try");
    test(null);
  }

  static void test(String str) {
    try {
      if(str == null) throw new NullPointerException();
      System.out.println(str);
    } catch (NullPointerException e) {
      System.out.println("catch");
    } finally {
      System.out.println("finally");
    }
  }

}

結果

try
finally
catch
finally

これはいいとして

package net.fernwelt;

public class Test {

  public static void main(String[] args) {
    System.out.println(Test.div(1));
    System.out.println(Test.div(0));
  }

  static String div(int n) {
    try {
      double dummy = 1 / n;
    } catch (ArithmeticException e) {
      return "catch";
    } finally {
      return "finally";
    }
  }

}

結果

finally
finally

な、なんだってー。

package test;

public class Test {

  public static void main(String[] args) {
    System.out.println(test("try"));
    System.out.println(test(null));
  }

  static int test(String str) {
    try {
      if (str == null)
        throw new NullPointerException();
      System.out.println(str);
    } catch (NullPointerException e) {
      System.out.println("catch");
      return -1;
    } finally {
      System.out.println("finally");
    }
    return 0;
  }

}

結果

try
finally
0
catch
finally
-1

finally は絶対に実行される。

try-catch-finally と到達不可コード

while 文などと同様に到達不可コードはコンパイルエラーとなる

package test;

public class Test {

  public static void main(String[] args) {
    try {
    } catch (Exception e) {
    } catch (Exception e) {
    }
  }

}
package test;

public class Test {

  public static void main(String[] args) {
    try {
    } catch (Exception e) {
    } finally {
      return;
    }
    return;
  }

}

throws で 例外をかわす

php だと例外は勝手に伝達されるけど、Java のチェック例外を処理せずに呼び出し元に伝達する場合は throws により処理しないことを明示的に示す必要がある。でも例外をかわすことは好まれない場合もあるようなので必要が無い場合は使わないほうがよさそう。

package test;

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

public class Test {

  static void test() throws IOException, FileNotFoundException {
    FileReader in = null;
    try {
      in = new FileReader("unexistance");
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }

  public static void main(String[] args) {
    try {
      test();
    } catch (IOException e) {
      System.out.println("キャッチしますた");
    }
  }

}

throw で 例外を投げる

例外を自分で投げる。うん、ただそんだけ。

package test;

public class Test {

  public static void main(String[] args) {

    try {
      throw new Exception("テスト");
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }

}

結果

テスト

throws を使って呼び出し元に例外を投げてもいい。 非チェック例外が発生した場合は throws に書かなくても呼び出し元に例外が投げられる。

例外クラスのメソッド

  • getStackTrace() スタックトレースを取得する
  • printStackTrace() スタックトレースを出力する
  • getMessage() エラーメッセージを取得する

例外・エラークラスの継承ツリー

  • Object <- Throwable <- Error
  • Object <- Throwable <- Exception <- RuntimeException

RuntimeException のサブクラス

説明
ArithmeticException算術例外
ArrayStoreException入れることのできない値を配列に入れた
ClassCastException不正なキャスト。ダウンキャストの失敗など。
IndexOutOfBoundsException想定される範囲外のインデックスへのアクセス。ArrayIndexOutOfBoundsExceptionなどのサブクラスがある。
IllegalArgumentException不正な引数による例外。NumberFormatExceptionなどのサブクラスがある。
IllegalStageException利用できない状態にあるものへのアクセス
NullPointerExceptionnullオブジェクトへのアクセス

例外を投げるメソッドのオーバーライド

package jp.fernweh.main;

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

class Base {
  public void method01() throws IOException {
    throw new IOException();
  }

  public void method02() throws IOException {
    throw new IOException();
  }

  public void method03() throws IOException {
    throw new IOException();
  }
}

class Extends extends Base {
  @Override
  public void method01() {
    // オーバーライドによりチェック例外の送出を無くしても
    // コンパイルエラーにはならない
  }

  @Override
  public void method02() throws Exception { /* Error */
    // オーバーライドにより送出するチェック例外の範囲を広げることはできない
    // (例外の親クラスを投げるようには変更できない)ので
    // 継承しているクラスを送出するように変更するとコンパイルエラーとなる
  }

  @Override
  public void method03() throws FileNotFoundException {
    throw new FileNotFoundException();
    // オーバーライドにより送出するチェック例外の範囲を小さくすることは可能
    // (元の例外のサブクラスならOK)なのでコンパイルエラーにはならない。
  }
}

StackTraceElement

例外インスタンスに格納されるスタックトレースデータの一要素を格納するクラス。

  • String declaringClass
  • String methodName
  • String fileName
  • String lineNumber

の4つのフィールドを持つだけの単純なクラスで、それぞれのゲッターから各フィールドの値を取得できる。

package jp.fernweh.main;

public class Sample {

  public static void main(String[] args) {
    try {
      thrower();
    } catch (Exception e) {
      StackTraceElement elements[] = e.getStackTrace();
      for (StackTraceElement element : elements) {
        System.out.println(element.toString());
        System.out.println("ClassName  :" + element.getClassName());
        System.out.println("MethodName :" + element.getMethodName());
        System.out.println("FileName   :" + element.getFileName());
        System.out.println("LineNumber :" + element.getLineNumber());
        System.out.println();
      }
    }
  }

  private static void thrower() throws Exception {
    throw new Exception("例外投げるよ");
  }
}

結果

jp.fernweh.main.Sample.thrower(Sample.java:17)
ClassName  :jp.fernweh.main.Sample
MethodName :thrower
FileName   :Sample.java
LineNumber :21

jp.fernweh.main.Sample.main(Sample.java:7)
ClassName  :jp.fernweh.main.Sample
MethodName :main
FileName   :Sample.java
LineNumber :7

例外を文字列に変換

以下のようにすると、例外のメッセージとスタックトレースのデータを文字列として変換して取得できる。

package jp.fernweh.main;

public class StackTraceString {

  public static void main(String[] args) throws Exception {
    try {
      thrower();
    } catch (Exception e) {
      System.out.println(exceptionToStackTraceString(e));
    }
  }

  private static void thrower() throws Exception {
    throw new Exception("例外投げるよ");
  }

  // 例外の内容を文字列にダンプする
  private static String exceptionToStackTraceString(Exception exception) {
    StringBuilder b = new StringBuilder(1024);
    b.append("Exception in thread \"");
    b.append(Thread.currentThread().getName());
    b.append("\" ");
    b.append(exception.toString());
    b.append("\n");

    StackTraceElement[] stackTrace = exception.getStackTrace();
    for (StackTraceElement element : stackTrace) {
      b.append("\tat ");
      b.append(element.toString());
      b.append("\n");
    }

    return b.toString();
  }
}

結果

Exception in thread "main" java.lang.Exception: 例外投げるよ
  at jp.fernweh.main.Sample.thrower(Sample.java:14)
  at jp.fernweh.main.Sample.main(Sample.java:7)

その他

  • double 型の数値は 0 で割ることができる
  • catch ブロックは省略できるけどコードの質的にどうなんだろうね
  • catch ブロックでコンソールに文字を出力する場合は System.out.print() ではなく System.err.print() を使うべきだそうな…例外は想定外のエラーじゃないからケースバイケースな気がするけど。

アサーション

assert 条件式;

というだけの構文。 Eclipse のデバッグで有効にする場合は Run > Debug Configurations... > Argumentsタブ > VA argumentsテキストエリア-ea というオプションを付けてやればアサーションが動作する。-ea オプションがないと無視されるので注意。

リリース時に高速な動作を期待するとかそういう場合なら static final boolean 定数を使用して条件分岐を行うと、 コンパイラが判断してバイトコードに含めないとかやってくれるっぽい。 しかしこれはこれで問題が起きることがあるっぽい (参考:Java の static 変数は奥が深い.... | | プログラマ2.0日報 | あすなろBLOG

Share
関連記事