ホームページ >Java >&#&チュートリアル >なぜ例外処理を行うのでしょうか? Java例外処理の仕組みを詳しく解説

なぜ例外処理を行うのでしょうか? Java例外処理の仕組みを詳しく解説

php是最好的语言
php是最好的语言オリジナル
2018-08-10 11:34:267687ブラウズ

1. 背景の紹介

なぜ例外処理を実行する必要があるのですか?

コンピュータ プログラムの場合、実行時にエラーが発生しないことは誰も保証できません。エラーの主な原因は次のとおりです:

  • コード エラー

  • 機器のエラーと物理学。制限事項: ディスクがいっぱい、メモリ オーバーフロー、ハードウェアの問題、ネットワークの中断...

  • プログラム エラー、どうやって解決しますか? Java 言語では、この問題に対処するために役立つ例外処理メカニズムが提供されています。

  • 例外メカニズムにより、プログラム内の例外処理コードを通常の業務コードから分離することができ、プログラムコードをよりエレガントにし、プログラムの堅牢性を向上させることができます。

Java の例外メカニズムは、主に try、catch、finally、throw and throws の 5 つのキーワードに依存しています。

波括弧は省略できません。try ブロックと呼ばれます。例外を引き起こす可能性のあるコードが含まれています。catch の後に、対応する例外タイプとコード ブロックが示されます。 catch ブロックは、このタイプのコード ブロックを処理するために使用されます。また、finally ブロックは、try ブロックで開かれた物理リソースをリサイクルするために使用されます。ブロックは常に実行されます。 throws キー throw という単語は主に、メソッドによってスローされる可能性のある例外を宣言するためにメソッド シグネチャで使用されます。 throw は、実際の例外をスローするために使用されます。例外オブジェクト。

簡単な例は以下の通り r
try {
   // args表示传入的形参,args[0]表示传入的第一个形参,args[1]表示传入的第二个形参
   int a = Integer.parseInt(args[0]);
   int b = Integer.parseInt(args[1]);
   int c = a / b;
   System.out.println("您输入的两个数相除的结果是:"+c);
} catch (IndexOutOfBoundsException ie){
   System.out.println("数组越界:运行程序时输入的参数个数不够");
} catch (NumberFormatException ne){
   System.out.println("数字格式异常:程序只能接收整数参数");
} catch (ArithmeticException ae){
   // 除0异常
   System.out.println("算术异常");
} catch (Exception e){
   System.out.println("未知异常");
}
E

2, Knowledge Analysis

2.1 例外処理の仕組み

2.1.1 Try ... Catch キャプチャ異常 { // 業務実装コード} Catch (Exception (Exception (Exception e ){

//例外処理コード

}

実行プロセス (またはロジック):

try ブロック内のコードがスムーズに実行できる場合は、try ブロック内のビジネス ロジック コードが「すべて正常」です。例外が発生すると、システムは自動的に例外オブジェクトを生成し、それが Java ランタイム環境に送信されます。このプロセスは例外のスローと呼ばれます。 Java ランタイム環境は例外オブジェクトを受け取ると、その例外オブジェクトを処理できる catch ブロックを探します。適切な catch ブロックが見つかった場合、例外オブジェクトは処理のために catch ブロックに渡されます。例外をキャッチします。Java ランタイム環境が例外をキャッチする catch ブロックを見つけられない場合、ランタイム環境は終了し、Java プログラムは終了します。

注: プログラム コード ブロックが try ブロック内にあるかどうか、または catch ブロック内のコードであっても、コード ブロックの実行時に例外が発生する限り、システムは常に例外オブジェクトを自動的に生成します。プログラムでこのコードの catch ブロックが定義されていない場合、Java ランタイム環境は例外を処理するための catch ブロックを見つけることができず、プログラムは終了します。

2.1.2 例外クラスの継承システム

Java ランタイム環境は、例外オブジェクトを受け取ると、その例外オブジェクトが例外クラスのインスタンスであるか、キャッチ ブロックの後でそのサブクラスであるかを判断します。環境 例外を処理するために catch ブロックが呼び出されます。そうでない場合、例外オブジェクトは次の catch ブロックで例外クラスと再度比較されます。

Java例外キャッチプロセス

画像からわかるように、tryブロックの後に複数のcatchブロックが存在する場合があります。これは、異なる例外クラスに異なる例外処理メソッドを提供するためです。システム内でさまざまな予期せぬ状況が発生すると、システムはさまざまな例外オブジェクトを生成し、Java ランタイムは、例外オブジェクトが属する例外クラスに基づいて、例外の処理にどの catch ブロックを使用するかを決定します。

通常の状況では、try ブロックが 1 回実行されると、try ブロックの後に catch ブロックは 1 つだけ実行され、複数の catch ブロックを実行することはできません。次のループを開始するためにループ内で continue が使用され、次のループで try ブロックが再実行されない限り、複数の catch ブロックが実行される可能性があります。

Javaの共通例外クラス間の継承関係

なぜ例外処理を行うのでしょうか? Java例外処理の仕組みを詳しく解説

Java把所有的非正常情况分为两种:异常和错误,它们都继承Throwable父类。

        Error错误,一般指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败、资源耗尽  等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象,也无须声明可能抛出Error及其任何子类对象。

常见异常:

IndexOutOfBoundsException:数组越界异常,原因在于运行程序时输入的参数个数不够

NumberFormatException:数字格式异常,原因在于运行程序时输入的参数不是数字,而是字母

ArithmeticException:除0异常

Exception:当发生未知异常时,该异常对象总是Exception类或其子类的实例,可用Exception对应的catch块处理该异常

NullPointerException:空指针异常,当试图调用一个null对象的实例或者实例变量时,会引发此异常

注:进行异常捕获时不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面,即先捕获小异常,再捕获大异常,否则将出现编译错误。(若父类在前,则排在它后面的子类的catch块将永远不会获得执行的机会,因为检索catch块是从上到下依次执行的)

2.1.3 Java7提供多异常捕获

在Java7之前,每个catch块只能捕获一种类型的异常,但从Java7开始,一个catch块可以捕获多种类型的异常。

使用一个catch块捕获多种类型的异常时需要注意如下两个地方:

  • 捕获多种类型的异常时,多种异常类型之间用 | 隔开。

  • 捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。

try {
         int a = Integer.parseInt(args[0]);
         int b = Integer.parseInt(args[1]);
         int c = a / b;
         System.out.println("您输入的两个数相除的结果是:"+c);
      } catch (IndexOutOfBoundsException|NumberFormatException|ArithmeticException e){
         System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
         
         // 捕获多异常时,异常变量默认有final修饰
         // 所以下面代码编译报错
//        e = new ArithmeticException("text");
      } catch (Exception e){
         System.out.println("未知错误");
         
         // 捕获一种类型的异常时,异常变量没有final修饰
         // 所有下面代码完全正确
         e = new RuntimeException("test");
      }

2.1.4 访问异常信息

         如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch后面括号中的异常形参来获得。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。

所有的异常对象都包含了如下几个常用方法:

  • getMessage():返回该异常的详细描述字符串。

  • printStackTrace():将该异常的跟踪栈信息输出到标准错误流。

  • printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。

  • getStackTrace():返回该异常的跟踪栈信息。

2.1.5 使用finally

 有时,程序在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显示回收。

注:Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。

         为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块是否被执行,甚至在try块或者catch块中执行了return语句,finally块总会被执行。

完整的Java异常处理语法结构:

try{

// 业务实现代码

} catch (Exception1 e){

// 异常处理块1

} catch (Exception2 e){

// 异常处理块2

} finally {

// 资源回收块

}      

例外処理構文構造では try ブロックのみが必要です。try ブロックがない場合、次の catch ブロックとfinally ブロックは存在できません。catch ブロックとfinally ブロックは両方ともオプションですが、catch の少なくとも 1 つは必要です。 1 つは、複数の catch ブロックを同時に出現させることもでき、親クラスの例外をキャッチする catch ブロックは、catch サブクラスの例外の後ろに配置する必要があります。 try ブロック、catch ブロックなし、finally ブロックなし。try ブロックの後に、finally ブロックをすべての catch ブロックの後に配置する必要があります。

注: 仮想マシンを終了するメソッドが try ブロックまたは catch ブロックで呼び出されない限り、try ブロックまたは catch ブロックでどのようなコードが実行されたか、またはどのような状況が発生したかに関係なく、例外処理のfinally ブロックが常に実行されます。 。通常の状況では、ergurn の使用を避けるか、メソッドを終了させる Final ブロッ​​クをスローするようにしてください。そうしないと、奇妙な状況が発生する可能性があります。

2.1.6 例外処理の入れ子

例外処理の処理全体がtryブロック、catchブロック、finallyブロックの中に含まれる状態を例外処理の入れ子と呼びます。

例外処理プロセスのコードは、実行可能コードを配置できる場所ならどこにでも配置できるため、完全な例外処理プロセスを try ブロック、catch ブロック、またはfinally ブロックに配置できます。

ネストされた例外処理の深さに明らかな制限はありませんが、通常は 2 レベルを超えるネストされた例外処理を使用する必要はありません。レベルが深すぎる場合は不要ですが、可読性が低下します。プログラム。

2.1.7 リソースを自動的に閉じる Java7 の try ステートメント

Java7 では、try ステートメントの機能が強化され、括弧の後に try キーワードを続けることができます。このリソースは、プログラムの最後に明示的に閉じる必要があるリソース (データベース接続、ネットワーク接続など) を指します。try ステートメントは、ステートメントの最後にこれらのリソースを自動的に閉じます。

注: try ステートメントがリソースを正常に閉じることができるようにするには、これらのリソース実装クラスが AutoCloseable または Closeable インターフェイスを実装する必要があります。これらの 2 つのインターフェイスを実装するには、close() メソッドを実装する必要があります。 (Closeable は AutoCloseable のサブインターフェイスです。Closeable インターフェイスの close() メソッドは IOException をスローすることを宣言するため、その実装クラスは close() メソッドの実装時に IOException またはそのサブクラスをスローすることのみを宣言できます。 AutoCloseable インターフェース () () メソッド宣言が例外をスローするため、その実装クラスは close() メソッドの実装時に異常を宣言できます)

リソースを自動的に閉じる TRY ステートメントは、非表示の Final ブロッ​​クを含むことと同等です (この Final ブロッ​​クは、 (リソースを閉じるため) なので、この try ステートメントには catch ブロックもfinally ブロックも含めることはできません。プログラムで必要な場合は、リソースを自動的に閉じる try ステートメントの後に、複数の catch ブロックとfinally ブロックを含めることができます。

Java7 では、ほぼすべての「リソースクラス」(各種ファイル IO クラス、JDBC プログラミング Connection、Statement およびその他のインターフェースを含む)が書き換えられ、書き換えられたリソースクラスはすべて AutoCloseable または Closeable インターフェースを実装しています。

2.2 チェック例外とランタイム例外のシステム

Java 例外は、チェック例外とランタイム例外 (ランタイム例外、一部は非チェック例外とも呼ばれます) の 2 つのカテゴリに分類されます。 RuntimeException クラスとそのサブクラスのすべてのインスタンスはランタイム例外と呼ばれます。RuntimeException クラスとそのサブクラスではない例外インスタンスはチェック例外と呼ばれます。 (使用時に例外の種類を特定するには、宣言された例外クラスを確認するだけでわかります)

Java 言語のみがチェック例外を提供し、他の言語はチェック例外を提供しません。 Java では、チェックされた例外は処理 (修復) できる例外であると考えられるため、Java プログラムはチェックされた例外を明示的に処理する必要があります。プログラムがチェックされた例外を処理しない場合、プログラムのコンパイル中にエラーが発生し、コンパイルに失敗します。

チェックされた例外を処理するには 2 つの方法があります:

    現在のメソッドは例外を処理する方法を明確に認識しており、プログラムは try...catch ブロックを使用して例外をキャッチし、対応する例外を修正する必要があります。キャッチブロック。
  • 現在のメソッドはこの例外を処理する方法を知らないため、メソッドを定義するときにこの例外をスローするように宣言する必要があります。
  • ランタイム例外はより柔軟です。プログラムがランタイム例外をキャッチする必要がある場合は、try...catch ブロックを使用して実装することもできます。

注: Java コアテクノロジー

        在Exception层次结构中,有两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。划分这两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。

          Java语言规范将派生于Error类或RuntimeException类的所有异常称为未(不受?)检查异常(unchecked),所有其他的异常称为已检查异常(checked)。(在编译期间进行检查,编译器将检查是否为所有的已检查异常提供了异常处理器)  ??????????? 有待讨论

2.3 使用throws抛出异常

       使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是平时我们的程序在遇到异常后自动结束的原因。

         throws声明抛出只能在方法签名中使用,throws可以声明多个异常类,多个异常类之间使用逗号隔开。

         一旦使用throws语句声明抛出异常,那么就无需使用try...catch块来捕获该异常了。

        使用throws声明抛出异常时有一个限制,就是方法重写时“两下”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。(即设置了上限)

使用Checked异常至少存在如下两大不便之处:

  • 对于程序中的Checked异常,Java要求必须显示捕获并处理该异常,或者显示声明抛出该异常,这增加了编程复杂度。

  • 如果在方法中显示声明抛出Checked异常,将会导致方法签名与异常耦合,如果该方法是重写父类的方法,则该方法抛出的异常还会受到被重写方法所抛出异常的限制。

         在大部分时候推荐使用Runtime异常,而不使用Checked异常,尤其当程序需要自行抛出异常时,使用Runtime异常将更加简洁。

         当使用Runtime异常时,程序无需在方法中声明抛出Checked异常,一旦发生了自定义错误,程序只管抛出Runtime异常即可。对于Runtime异常,Java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。

         使用Runtime异常省事,但Checked异常也有其优势,Checked异常能在编译时提醒程序员代码可能存在的问题,提醒程序员必须注意处理该异常,或者声明该异常有方法的调用者来处理,从而避免程序员因为粗心而忘记处理该异常的错误。

2.4 使用throw抛出异常

       当程序出现错误时,系统会自动抛出异常,除此之外,Java也允许程序自行抛出异常,自行抛出异常使用throw语句来完成。

2.5 自定义异常类

       用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器;另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(即异常对象的getMessage()方法的返回值)。

package AuctionException;

// 自定义异常都要继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。
// 自定义异常类需要提供两个构造器:一个是无参数的构造器;另一个是带一个字符串参数的构造器,这个字符串作为
// 该异常对象的描述信息(即异常对象的getMessage()方法的返回值)
public class AuctionException extends Exception{
   
   //1、无参数的构造器
   public AuctionException(){
   }
   //2、带一个字符串参数的构造器
   public AuctionException(String msg){
      // 通过super调用父类的构造器
      super(msg);
   }
}

2.6 catch和throw同时使用

       在实际应用中,当一个异常出现时,单靠某个方法无法完全处理该异常,必须由几个方法协作才可以完全处理该异常。也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获到异常。

         为了实现这种通过多个方法协作处理同一个异常的情形,可以在catch块中结合throw语句来完成。

         这种catch和throw结合使用使用的情况在大型企业级应用中非常常用,企业级应用对异常的处理通常分成两个部分:一是后台需要通过日志来记录异常发生的详细情况;二是应用还需要根据异常向应用使用者传达某种提示。

package CatchAndThrow;

import AuctionException.AuctionException;

public class CatchAndThrow {
   
   private double initPrice = 30.0;
   
   // 因为该方法中显示抛出了AuctionException异常
   // 所以此处需要声明抛出AuctionException异常
   public void bid(String bidPrice) throws AuctionException{
      double d = 0.0;
      try {
         d = Double.parseDouble(bidPrice);
      } catch (Exception e){
         
         // 此处完成本方法中可以对异常执行的修复处理
         // 此处仅仅是控制台打印异常的跟踪栈信息
         e.printStackTrace();
         
         // 再次抛出自定义异常
         throw new AuctionException("竞拍价必须是数值,不能包含其他字符");
      }
      
      if (initPrice > d){
         throw new AuctionException("竞拍价比起拍价低,不允许竞拍");
      }
      
      initPrice = d;
   }
   
   public static void main(String[] args){
      
      CatchAndThrow catchAndThrow = new CatchAndThrow();
      try{
         catchAndThrow.bid("df");
      } catch (AuctionException ae){
         // 再次捕获到bid()方法中的异常,并对该异常进行处理,此处是将异常的详细描述信息输出到标准错误(err)输出
         System.err.println(ae.getMessage());
      }
   }
}

2.7 异常跟踪栈

       异常对象的printStackTrace()方法用于打印异常的跟踪栈信息,根据printStackTrace()方法的输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。

3、常见问题

3.1 为什么在finally块中不能访问try块中声明的变量?

答:try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块及finally块中不能访问该变量。

3.2 为什么最好不要使用catch all语句?

答:所谓catch all语句指的是一种异常捕获模块,它可以处理程序发生的所有可能异常。例如使用Exception或者Throwable类捕获所有异常,虽然这种方式能够处理异常,但是它不能精确描述引发异常的原因,我们很难进行准确的排查。

3.3 有没有关于注解方式的异常处理?

答:有的,比如spring有基于注解的全局异常处理方式(使用@ExceptionHandler)

4、参考文献

摘自《疯狂Java讲义 》第三版,文中代码模仿书中例子所敲。

今天的分享就到这了,希望大家多多指正,互相学习~

相关推荐:

java异常(Exception)处理机制示例代码分享

简单介绍Java的异常和架构

以上がなぜ例外処理を行うのでしょうか? Java例外処理の仕組みを詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。