1. 例外の種類

例外の種類

すべての例外は 4 つのタイプに分類され、実際には相互に継承するクラスです。

Throwableクラス

すべての例外の基本クラスはクラスですThrowableこのThrowableクラスには、現在の呼び出しスタック (現在のメソッドのスタック トレース) を配列に書き込むコードが含まれています。スタック トレースとは何かについては、少し後で学びます。

throw演算子は、クラスから派生したオブジェクトのみを受け入れることができますThrowable理論的には のようなコードを書くこともできますがthrow new Throwable();、通常は誰もこれを行いません。このクラスの主な目的はThrowable、すべての例外に対して単一の親クラスを持つことです。

Errorクラス

次の例外クラスはError、クラスを直接継承するクラスですThrowable重大な問題が発生した場合、 Java マシンはErrorクラス (およびその子孫)のオブジェクトを作成します。たとえば、ハードウェアの故障、メモリ不足などです。

通常、プログラマーとして、このようなエラー (エラーがスローされるべき種類) がプログラム内で発生した状況では、何もすることができません。これらのエラーは深刻すぎるためです。Errorできることは、プログラムがクラッシュしていることをユーザーに通知するか、エラーに関する既知の情報をすべてプログラム ログに書き込むことだけです。

Exceptionクラス

およびクラスは、多くのメソッドの操作中に発生する一般的なエラーに対応しますExceptionスローされた各例外の目標は、それを適切に処理する方法を知っているブロックによってキャッチされることです。RuntimeExceptioncatch

メソッドが何らかの理由で作業を完了できない場合、適切なタイプの例外をスローして呼び出し側メソッドに直ちに通知する必要があります。

つまり、変数が に等しい場合null、メソッドは をスローしますNullPointerException。不正な引数がメソッドに渡された場合、メソッドは をスローしますInvalidArgumentException。メソッドが誤ってゼロで除算した場合、 がスローされますArithmeticException

RuntimeExceptionクラス

RuntimeExceptionsは のサブセットですExceptionsRuntimeExceptionこれは通常の例外 ( ) の軽量バージョンであるとさえ言えますException。そのような例外に課される要件と制限は少なくなります。

Exceptionとの違いについては後で学びますRuntimeException


2. Throws:チェックされた例外

スロー: チェックされた例外

すべての Java 例外は、「チェック済み」と「チェックなし」の2 つのカテゴリに分類されます。

RuntimeExceptionまたはを継承するすべての例外は、未チェック例外Errorとみなされます。 その他はすべてチェック例外です。

重要!

チェック例外が導入されてから 20 年が経過しましたが、ほとんどすべての Java プログラマーがこれをバグだと考えています。一般的な最新のフレームワークでは、すべての例外の 95% がチェックされていません。Java をほぼ正確にコピーした C# 言語は、チェック例外を追加しませんでした。

チェック例外とチェックなし例外の主な違いは何ですか?

チェック例外には追加の要件が課されます。大まかに言うと、これらは次のとおりです。

要件1

メソッドがチェック例外をスローする場合、そのシグネチャで例外のタイプを示す必要がありますそうすることで、それを呼び出すすべてのメソッドは、この「意味のある例外」がその中で発生する可能性があることを認識します。

キーワードの後のメソッド パラメータの後にチェック例外を示します(キーワードを誤って使用しないでください)。次のようになります。throwsthrow

type method (parameters) throws exception

例:

チェックされた例外 未チェックの例外
public void calculate(int n) throws Exception
{
   if (n == 0)
      throw new Exception("n is null!");
}
public void calculate(n)
{
   if (n == 0)
      throw new RuntimeException("n is null!");
}

右側の例では、コードは未チェックの例外をスローします。追加のアクションは必要ありません。 左側の例では、メソッドがチェック済み例外をスローするため、throws例外のタイプとともにキーワードがメソッド シグネチャに追加されます。

メソッドが複数のチェック例外をスローすることを予期している場合は、それらすべてをthrowsキーワードの後に​​カンマで区切って指定する必要があります。順序は重要ではありません。例:

public void calculate(int n) throws Exception, IOException
{
   if (n == 0)
      throw new Exception("n is null!");
   if (n == 1)
      throw new IOException("n is 1");
}

要件2

シグネチャ内にチェック例外が含まれるメソッドを呼び出す場合、例外がスローされるという事実を無視することはできません。

catchこのような例外をすべてキャッチするには、例外ごとにブロックを追加するか、メソッドの句にブロックを追加する必要があります。throws

それはまるで、「これらの例外は非常に重要なので、必ずキャッチしなければなりません。そして、それらの処理方法がわからない場合、メソッドを呼び出す可能性のある人には、そのような例外が発生する可能性があることを通知する必要があります。」と言っているかのようです。

例:

人間が住む世界を作成するメソッドを作成していると想像してください。初期人数は引数として渡されます。したがって、人数が少なすぎる場合は例外を追加する必要があります。

地球を創る ノート
public void createWorld(int n) throws EmptyWorldException, LonelyWorldException
{
   if (n == 0)
      throw new EmptyWorldException("There are no people!");
   if (n == 1)
      throw new LonelyWorldException ("There aren't enough people!");
   System.out.println("A wonderful world was created. Population: " + n);
}
このメソッドは、次の 2 つのチェック例外をスローする可能性があります。

  • EmptyWorldException
  • LonelyWorldException

このメソッド呼び出しは 3 つの方法で処理できます。

1. 例外をキャッチしない

これは、メソッドが状況を適切に処理する方法を知らない場合に最も頻繁に行われます。

コード ノート
public void createPopulatedWorld(int population)
throws EmptyWorldException, LonelyWorldException
{
   createWorld(population);
}
呼び出し側メソッドは例外をキャッチしないため、例外を他のメソッドに通知する必要があります。例外を独自のthrows句に追加します。

2. いくつかの例外をキャッチする

対応できるエラーには対応いたします。しかし、理解できないものは、呼び出し側のメソッドにスローします。これを行うには、throws 節に名前を追加する必要があります。

コード ノート
public void createNonEmptyWorld(int population)
throws EmptyWorldException
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
}
呼び出し元は、チェックされた例外を 1 つだけキャッチします — LonelyWorldException。他の例外は、その署名にthrowsキーワードの後に​​それを示すように追加する必要があります。

3. すべての例外をキャッチする

メソッドが呼び出し側メソッドに例外をスローしない場合、呼び出し側メソッドは常にすべてが正常に動作したと確信します。そして、例外的な状況を修正するための措置を講じることができなくなります。

コード ノート
public void createAnyWorld(int population)
{
   try
   {
      createWorld(population);
   }
   catch (LonelyWorldException e)
   {
      e.printStackTrace();
   }
   catch (EmptyWorldException e)
   {
      e.printStackTrace();
   }
}
すべての例外はこのメソッドでキャッチされます。電話をかけてきた人は、すべてがうまくいったと確信するでしょう。


3. ラッピング例外

チェック例外は理論的にはクールに見えましたが、実際には大きなフラストレーションとなることが判明しました。

プロジェクトに非常に人気のあるメソッドがあるとします。これはプログラム内の何百もの場所から呼び出されます。そして、新しいチェック例外をそれに追加することにしました。そして、このチェック例外は非常に重要で特別なので、それがキャッチされた場合に何をすべきかをメソッドだけが知っている可能性がありますmain()

つまり、非常に人気のあるメソッドを呼び出すすべてのメソッドの句チェック例外を追加する必要がありますthrows。同様に、throwsそれらのメソッドを呼び出すすべてのメソッドの句でも同様です。また、それらのメソッドを呼び出すメソッドについても同様です。

その結果、throwsプロジェクト内のメソッドの半分の句に新しいチェック例外が発生します。そしてもちろん、プロジェクトはテストの対象になっていますが、テストはコンパイルされません。そして今度は、テスト内の throws 句も編集する必要があります。

そして、すべてのコード (数百のファイルのすべての変更) を他のプログラマーがレビューする必要があります。そしてこの時点で、なぜプロジェクトにこれほど多くの血なまぐさい変更を加えたのかを自問します。数日の作業と壊れたテスト - すべては 1 つのチェック例外を追加するためでしょうか?

そしてもちろん、継承とメソッドのオーバーライドに関連する問題はまだ残っています。チェック例外によって生じる問題は、利点よりもはるかに大きいです。肝心なのは、今ではそれらを愛する人も使用する人もほとんどいないということです。

ただし、これらのチェック例外を含むコード (標準 Java ライブラリ コードを含む) が依然として多数存在します。彼らに対して何をすべきでしょうか?私たちはそれらを無視することはできませんし、どのように対処すればよいのかわかりません。

Java プログラマーは、チェックされた例外を でラップすることを提案しましたRuntimeException。つまり、すべてのチェック済み例外をキャッチしてから、非チェック例外 (たとえば、RuntimeException) を作成し、代わりにそれらをスローします。これを実行すると次のようになります。

try
{
   // Code where a checked exception might occur
}
catch(Exception exp)
{
   throw new RuntimeException(exp);
}

これはあまりきれいな解決策ではありませんが、ここには何も犯罪的なものはありません。例外は単に .html の中に詰め込まれているだけですRuntimeException

必要に応じて、そこから簡単に取得できます。例:

コード ノート
try
{
   // Code where we wrap the checked exception
   // in a RuntimeException
}
catch(RuntimeException e)
{
   Throwable cause = e.getCause();
   if (cause instanceof Exception)
   {
      Exception exp = (Exception) cause;
      // Exception handling code goes here
   }
}







オブジェクト内に格納されている例外を取得しますRuntimeException。変数は、 その型を決定し、それをチェックcause例外型に変換する可能性があります。 null



4. 複数の例外をキャッチする

プログラマーはコードを複製することを非常に嫌います。彼らは、対応する開発原則「 Don't Reply Yourself (DRY)」も考案しました。ただし、例外を処理する場合、1 つのブロックの後に同じコードを持つ複数のブロックが続くことがよくあります。trycatch

catchまたは、同じコードを持つ3 つのブロックと、別の同じコードを持つ別の 2 つのブロックが存在する可能性がありますcatch。これは、プロジェクトが責任を持って例外を処理する場合の標準的な状況です。

バージョン 7 以降、Java 言語には、単一のcatchブロックで複数のタイプの例外を指定する機能が追加されました。次のようになります。

try
{
   // Code where an exception might occur
}
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 name)
{
   // Exception handling code
}

catchブロックは必要な数だけ持つことができます。ただし、単一のcatchブロックで、相互に継承する例外を指定することはできません。つまり、クラスは を継承するため、catch ( Exception| RuntimeExceptione)を記述することはできません。RuntimeExceptionException



5. カスタム例外

いつでも独自の例外クラスを作成できます。クラスを継承するクラスを作成するだけですRuntimeException。次のようになります。

class ClassName extends RuntimeException
{
}

詳細については、OOP、継承、コンストラクター、メソッドのオーバーライドを学習する際に説明します。

ただし、このような単純なクラスしかない場合でも (コードがまったくない場合)、それに基づいて例外をスローすることができます。

コード ノート
class Solution
{
   public static void main(String[] args)
   {
      throw new MyException();
   }
}

class MyException extends RuntimeException
{
}




チェックされていない を スローしますMyException

Java マルチスレッドのクエストでは、独自のカスタム例外の操作について詳しく説明します。