JavaSilver_躓いた問題をまとめてみた③ 例外処理その2

f:id:nowa0402:20210520184724p:plain

こんにちは。のわです。
今回は現在勉強中のJavaSilverから
例外について引き続きまとめたいと思います。

前回の記事で簡単に構文について記載しました。
nowa-0402.hatenablog.com


今回は例外の伝播と例外を発生させる方法について記述します。


例外の伝播とは
「あるメソッドが違うメソッドを呼び出したとき
 そのメソッドで例外が発生し、例外処理されなかった場合
 呼び出し元まで例外がたらい回しにされる状況」
のことです。

言葉だと長いですね...
例えば mainメソッドがsubメソッドを呼び出すコードを例にしてみましょう。

public class Main{
  public static void main(String[] args) {
    sub();
    System.out.println("処理を終了します");
  }

  public static void sub() {
    String a = null;
    System.out.println(a.length());
  }
}

上記コードでは
①mainメソッドがsubメソッドを呼び出す
②subメソッドは変数aに入っている文字列数を出力する
③「処理を終了します」と出力される

しかし、subメソッドの変数aにはnullが入っています。
lengthメソッドでnullを参照しようとしているため
② の処理中に「NullPointerException」の例外が発生します。


この時、上記コードでは何が起きるか
①subメソッドは発生した例外をキャッチしようとするが、try-catch文が無いためキャッチできない
②キャッチ出来なかった例外をmainメソッドにわたす
③mainメソッドはsubメソッドから例外が来るが、try-catch文がないためキャッチできない
④誰もキャッチできずプログラムが強制終了する


という処理になっています。
まさに「例外がたらい回し」にされている状態です。
「処理が終了します」までたどり着けませんでした。


上記のような単純なコードなら強制終了でもいいですが
インフラシステムのようなプログラムで簡単に強制終了されたら大変なことになりますね。。。


では、どうすればよいか
解決策は2つです。
①subメソッドが例外を処理する
②呼び出し元のmainメソッドが処理する

①subメソッドが例外を処理する。
この場合だと下記の様なコードになります。

public class Main{
  public static void main(String[] args) {
    sub();
    System.out.println("処理を終了します");
  }

  public static void sub() {
    try{
    String a = null;
    System.out.println(a.length());
    } catch(NullPointerException e) {
      System.out.println("エラーが発生しました");
    }
  }
}

このとき、subメソッドは『自ら起こした例外を自ら処理する』状態ともいえます。



②呼び出し元のmainメソッドが処理する
だとどうなるでしょうか。

public class Main{
  public static void main(String[] args) {
    try {
    sub();
    } catch (NullPointerException e) {
      System.out.println("subメソッドで異常が発生しました");
    }
    System.out.println("処理を終了します");
  }

  public static void sub() throws NullPointerException{
    String a = null;
    System.out.println(a.length());
  }
}

見慣れないワードが出てきました
『throws』はスロー宣言と呼ばれるものです。
この宣言をしているメソッドはtry-catch文の記述をしなくても良くなります。
その代わり、スロー宣言をしているメソッドを呼び出す側でtry-catch文を使う必要があります。

簡単にいうなら
「私を呼び出して何かあったら、呼び出したあなたが責任持って処理してくださいね」
という状態です。


※上記コードですが、try-catch文を必ずしも書く必要は無い例外です。
 try-catchが必須なのはException系になります。
 NullPointerExceptionRuntimeException系と呼ばれる例外種類になります。
 この例外は必ずしもtry-catch文を書く必要がないものです。
 今回は簡単コードを記述するためにRuntimeException系を例にあげました。



では、次に例外を自力で発生させる方法を見ていきましょう。

public class Main{
  public static void main(String[] args) {
    String a = null;
    if(a == null) {
      throw new NullPointerException("値がnullです");
    }
  }
}

『throw』は『例外的状況の報告』をする構文になります。
上記コードの場合
throw new 例外クラス("任意のエラーメッセージ")
という記述になります。

この例外的状況の報告は「私達がJVMに例外を知らせるコード」です。
逆に、今まで記述してきた例外は「JVMが私達に例外を知らせるコード」でした。
これを『例外を投げる』といいます。



今回は例外についてまとめてみました。
 要点のまとめです。
◇例外が発生する可能性がある処理をおこなうとき以下のどちらかで対応する必要がある
  ・呼び出し先でtry-catch文を記述する
  ・呼び出し先でスロー宣言(throws)を行い、呼び出し元で例外を処理させる

◇例外を投げるためにはthrow 文を使う

今回も最後まで読んでいただきありがとうございました!!





以下余談です。

JavaSilver用です。
例外の伝播は永遠にたらい回しにすることができます。

public class Main {
  public static void main(String[] args) {
      try{
      sub();
      } catch(Exception e) {
        System.out.println("異常発生");
      }
      System.out.println("処理を終了します");
  }

  public static void sub() throws Exception {
    System.out.println("subsubメソッドを呼び出します");
    subsub();
  }

  public static void subsub() throws Exception {
    System.out.println("例外を作り出します");
    throw new Exception("例外です");
  }

}

Exception系の例外はどこかしらでキャッチする必要があります。
このコードでは例外処理をmainメソッドで行っています。

では、下記コードはどうでしょうか

public class Main {
  public static void main(String[] args) throws Exception{
      sub();
      System.out.println("処理を終了します");
  }

  public static void sub() throws Exception {
    System.out.println("subsubメソッドを呼び出します");
    subsub();
  }

  public static void subsub() throws Exception {
    System.out.println("例外を作り出します");
    throw new Exception("例外です");
  }

}

上記コードを見ると最終的な呼び出し元のmainメソッドもスロー宣言をしています。
この場合、コンパイルエラーになりそうですが、ならずに実行することができます。
ただし、「処理を終了します」まで処理が行われる前に強制終了になります。


うーん...例外って難しいですね...