Java异常处理方法及流程,java异常处理规范和注意事项

  Java异常处理方法及流程,java异常处理规范和注意事项

  前言在理想的世界里,程序永远不会有问题,用户输入的数据永远正确,逻辑不会有问题,选择打开的文件也必须存在,内存永远足够……!但在现实世界中,一旦出现这些问题,如果处理不当,程序将无法正常运行,从而影响用户体验,用户可能再也不会使用这个程序。

  出现异常时,应向外界给出清晰友好的提示信息。在内部,程序应该尽力自己采取补救措施。如果不能,就要及时释放被占用的资源,以免影响其他线程的任务,导致整个程序崩溃。所以程序的异常处理非常重要。

  在学习编程的初期,为了快速学习语法,我们编写一些可以按预期实现的程序。但是,我们在实际开发项目的时候,也不能太简单。一定要处处小心,质疑别人自己写的程序,甚至其他第三方库程序——出了问题怎么办?直接忽视会让我丢掉公司的工作吗?

  当程序出错时,Java使用异常机制,支持封装错误信息,让程序跳出正常处理流程,交给异常处理部分处理。说到异常处理,很多人会想到Try- Catch语法。让我们通过一个简单的代码示例来运行和解释这个示例,快速了解一下什么是异常以及异常处理的行为习惯。

  Try Catch语句在Java程序中,Try Catch语句用于处理程序异常。首先,看下面这个简单的例子来理解同一个Try Catch语句的执行过程。

  package com . example . learn exception;

  公共类异常FirstExpression {

  公共静态void main(String[] args) {

  尝试{

  int[]arr=new int[1];

  arr[1]=9;

  } catch (Exception ex) {

  int abc=999

  ex . printstacktrace();

  }

  尝试{

  string str=“”;

  str.substring(9,10);

  } catch (Exception ex) {

  ex . printstacktrace();

  }

  System.out.println(“程序执行结束”);

  }

  }在解读以上程序执行结果之前,先了解一下try catch语句异常处理的执行过程。

  在try语句中,如果出现不符合正确预期的异常,程序的执行流程将跳转到catch语句。Java会将异常相关的信息封装在异常类的一个实例中,上面catch代码块中的ex变量就是对这个异常实例的引用。处理异常最简单的方法是调用异常实例的printStackTrace方法,打印出异常信息并输出到控制台或日志。当catch块中的语句完成时,程序将继续以向下的顺序执行。上面的程序有两个托盘catch块,您可以看到两个try块中的语句都有问题。第一个是数组索引越界(数组长度为1,索引只有0),第二个是字符串索引越界。它们都将导致程序抛出异常。让我们执行程序来看看结果:

  Java . lang . arrayindexoutofboundsexception:1

  at com . qsc . ebao . insure plan . controller . front . exception first expression . main(exception first expression . Java:7)

  Java . lang . stringindexoutofboundsexception:字符串索引超出范围:10

  位于Java . lang . string . substring(string . Java:1963)

  at com . qsc . ebao . insure plan . controller . front . exception first expression . main(exception first expression . Java:16)

  在程序执行结束时,我们可以看到程序仍然在程序执行的最后,但是Java . lang . arrayindexoutofboundsexception和Java . lang . stringindexoutofboundsexception这两个异常在两个catch语句中被捕获,它们的实例被赋给了ex。在例程的catch块的异常处理中使用,ex.printStackTrace打印出异常详细信息和导致异常的调用堆栈。

  因为异常被正常捕捉和处理,所以上述例程仍然可以顺利执行到最后。

  了解了try catch语句的执行过程后,我们再来深入了解一下Java中异常实例所代表的异常的体系。

  Java中的异常分类是由Throwable类及其子类来描述的。Throwable类是Java异常类型的顶级父类。只有当一个对象是Throwable类或其子类的实例时,它才是一个异常对象,并且可以被Java的异常处理机制识别。

  Java提供了很多系统异常,我们也可以自定义异常。但是在Java的异常分类中,我们只需要记住几个关键的分类,就不用去背异常类的所有行为了。

  下图是一个异常类族的层次图。

  Throwable有两个直接子类,Error类和Exception类。

  错误类别表示系统的内部错误和资源耗尽错误。这些错误发生在虚拟机本身,或者在虚拟机尝试执行应用程序时发生。这些异常超出了应用程序的控制和处理能力。一旦发生,Java虚拟机一般会选择终止线程。Exception类指示程序可以处理的异常,该异常可以被捕获并可能被恢复。当遇到这种异常时,要尽力处理好异常,让程序恢复运行,而不是随意终止异常。运行时异常及其子类是Runtime Exception,即未检查的异常。Exception的其他子类是非运行时异常,即检查异常。其实你不用记住以上变态类的分类。如果你有困惑,你可以回到这里再复习一遍。真正对我们的程序编写有影响的需要我们牢记的是上面描述中提到的未检查异常和检查异常,这是根据Java的异常处理要求分类的。

  未检查的异常):Error和RuntimeException及其子类都是未检查的异常。Java并不强迫你在程序中使用try catch或throws来处理这样的异常。出现这种异常的原因,大多是代码写的有问题。比如除数为0时的error ArithmeticException,类型强制转换时的error ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,空对象NullPointerException等等。检查异常:除了上述非检查异常,其他所有异常类都属于检查异常。Java强制用try catch语句在程序的方法中捕捉它,或者用throws语句把它扔到外层,否则编译不会通过。这种异常一般是程序运行环境造成的。程序可能运行在各种未知的环境中,它无法干涉用户如何使用我们编写的程序,所以程序要做好处理这类异常的准备。如sqlexception、ioexception、classnotfoundexception等。当在try块中执行程序时,下面的例程将引发ClassNotFoundException。它是一个已检查的异常,如果它不是由try catch处理或声明为引发此异常,则无法编译。

  package com . example . learn exception;

  必须检查公共类{

  //这里可以选择throws和try catch中的一个,try catch之后throws就没有机会执行了。

  公共静态void main(String[] args)引发ClassNotFoundException {

  尝试{

  class clazz=class . forname( com . example . learn exception . mustcheckedabc );

  } catch(ClassNotFoundException e){

  e . printstacktrace();

  }

  }

  }在这个示例程序中,其实我们已经知道了Java程序的方法中遇到异常时用完异常的语法。我们再来分析一下抛出异常的使用场景。

  抛出异常抛出异常可以分为抛出方法中使用的其他包产生的异常(再抛出)——即不处理,然后抛出,交给上级处理。并抛出您自己代码的异常。

  您可以使用throws关键字来声明方法可能在方法上引发的异常。抛出的异常类型可以是实际异常的父类或自身。Throws关键字可以声明抛出多种类型的异常,只是用逗号分隔。

  公共类ThrowIt {

  公共静态void main(String[] args)抛出ClassNotFoundException,NoSuchFieldException {

  class clazz=class . forname( ABC );

  clazz . getfield( );

  }

  }以上是方法可能抛出ClassNotFoundException和NoSuchFieldException异常的语句。

  我们也可以自己创建一个异常并抛出,在代码中主动抛出异常,使用throw语句(注意是单数,不要和方法声明上的throws混淆)。

  package com . example . learn exception;

  公共类NewAndThrowIt {

  公共静态void causeException()引发异常{

  int a=1;

  如果(a 10) {

  引发新异常(“Custome Exception”);

  }

  }

  公共静态void causeRuntimeException()引发RuntimeException {

  //可以创建一个未检查的异常,然后用throw语句将其抛出。

  //此时,方法定义中可能有也可能没有throws语句。

  引发新的runtime exception(“”);

  }

  }方法causeException创建一个检查过的异常实例,然后用throw关键字抛出。此时,需要在方法声明中显式声明方法将抛出的异常类型。方法causeRuntimeException创建一个未检查的异常,然后用throw关键字引发它。此时,方法声明中可能有也可能没有throws语句。定义接口时,还可以在接口方法声明中添加throws语句,以限制实现类在抛出异常时抛出该类或其子类。

  公共接口IntfaceWithEx {

  void methodWithEx()引发异常;

  }异常传输和异常链在现实世界中,用Java开发的程序都是由类组成的,而类的功能是通过方法实现的,所以一个功能是通过一层一层地调用方法来实现的。异常处理中最基本的ex.printStackTrace()就是异常发生时方法的调用栈——也就是可以理解为事发现场。

  Java的异常也是在方法调用栈上传递的,要么沿着方法调用栈一路抛出到上层方法,最后在没人处理的情况下当前线程异常退出,要么被调用栈中的某个方法捕获。

  异常处理的一个常见场景是,比如底层io发生异常,我们的程序可能会捕捉到这个异常,然后根据自己的需要抛出一个新的异常。如果此时只是简单的把已经创建好的新异常抛出,必然会导致原有异常信息的丢失,那么如何才能保留整个异常链接呢?Throwable及其子类可以在其重载构造函数中接受Throwable类型的原因参数。

  公共类异常扩展Throwable {

  公共异常(字符串消息){

  超级(消息);

  }

  公共异常(字符串消息,引发原因){

  超级(消息,原因);

  }

  .

  }

  公共类Throwable实现Serializable {

  .

  公共同步可抛出getCause() {

  return(原因==这个?null:原因);

  }

  }该参数代表原始异常,可以通过getCause()获取原始异常信息。这种方法可以保持整个异常链的连续性。

  自定义异常通常需要定义一些您在编写应用程序时需要的异常。异常最重要的信息有三条:类型、错误信息和调用栈。

  类型,在出现catch、throws、throw等关键字的地方,会给出一个错误消息来判断异常的类型,描述异常的细节。调用堆栈,发生异常时的调用堆栈。调用栈是在异常基类Throwable的帮助下封装的,不需要程序员和开发人员的人工干预。下面是一个自定义异常类UserException,仿照异常类的重载构造方法,我们也为它定义了三个重载构造方法。

  package com . example . learn exception . exceptions;

  公共类UserException扩展异常{

  公共UserException() {

  }

  公共用户异常(字符串消息){

  超级(消息);

  }

  public UserException(字符串消息,引发原因){

  超级(消息,原因);

  }

  公共用户异常(可抛出原因){

  超(因);

  }

  }构造方法中Throwable type的参数cause在上一节的异常链中已经提到了,表示导致当前异常的异常。比如在创建User时,数据库网络问题导致创建失败,那么在抛出UserException异常时,可以将数据库IO异常传递给UserException,作为创建UserException异常实例的原因。

  下面是一个使用我们的自定义异常UserException的示例程序。

  package com . example . learn exception;

  导入com . example . learn exception . exceptions . user exception;

  公共类交换程序{

  public void callThrowException()抛出UserException {

  //可以在这里捕捉异常,然后封装成自己的异常,并添加相应的异常描述。

  system . out . println( ex caller . callthrowexception starts );

  尝试{

  class . forname( com . example . testforexception );

  } catch(ClassNotFoundException ex){

  抛出new UserException(有问题,ex);

  }

  系统。out.println (Excaller。CallThrowException方法调用结束’);

  }

  }在上面的例程中,我们在方法中创建了TestForException类的一个实例。因为该类不存在,所以将在try块中引发ClassNotFoundException异常。在catch中,我们捕获这个异常,将其封装为我们自己的异常,并添加相应的异常描述。

  package com . example . learn exception;

  导入com . example . learn exception . exceptions . user exception;

  公共类CallExceptionAppMain {

  公共静态void main(String[] args) {

  ex caller caller=new ex caller();

  尝试{

  caller . callthrowexception();

  } catch (UserException ex) {

  ex . printstacktrace();

  }

  }

  }catch语句根据异常类型匹配捕捉相应类型的异常。如果类型不匹配,catch语句将不会执行,异常将继续被抛出。

  也就是说,如果使用catch (Throwable),那么所有的异常,包括Error,都会被捕获。但建议catch的范围尽量精确,不要使用范围太大的异常类的父类。例如,在捕获时应该尽可能少地使用异常。如果不可能,最多只需要捕捉异常类型的异常。不要使用Throwable。

  注意:

  如果您捕获到一个实际上不太可能抛出的检查异常,Java程序将报告一个错误,因为Java清楚地知道这种类型的异常不会发生。如果catch是非检查异常,Java程序将不会报告错误。如果抛出未实际抛出的已检查异常或未检查异常,Java程序将不会报告错误。掌握了自定义异常之后,让我们回到Try-Catch语法。我们上面一直在用的,其实并不是这个语法的完整状态。下一节再详细说吧。

  Try Catch Finallytry catch语句的形式可以扩展为try catch finally。让我们通过一个示例程序来看看finally的效果。

  package com . example . learn exception;

  公共类TryCatchFinallyAppMain {

  私有静态int VAL=0;

  公共静态void main(String[] args) {

  system . out . println(with finally());

  system . out . println(VAL);

  }

  私有静态int withFinally() {

  int len=0;

  尝试{

  字符串s=null

  返回s . length();

  } catch (Exception ex) {

  //异常处理:如果有返回值,会返回一个特殊值,表示情况不对,出现异常。

  len=-1;

  System.out.println(执行catch中的返回语句);

  返回len

  }最后{

  //可以认为finally语句会在方法返回之后,后面的方法开始之前,跟在return语句后面。

  //无论是由于返回结束还是异常结束,finally语句都会被执行。

  system . out . println( Execute finally语句);

  //最后,最好不要有返回语句。

  //return-2;

  //最后,在前面的返回表达式中给变量赋值是没有用的。

  len=-2;

  VAL=999

  System.out.println(finally语句结束);

  }

  }

  }无论是否产生异常,finally块中的代码都会被执行,无论是因为返回结束还是异常结束,finally语句都会在方法退出后执行。Finally是在return之后的表达式操作之后执行的,所以函数的返回值是在finally执行之前确定的。无论finally中的代码是什么,返回值都不会改变,还是之前的return语句中保存的值,所以我们不要试图在finally的代码块中修改要返回给调用者的返回值。还有一点需要注意的是,finally中最好不要包含return,否则程序会提前退出,返回值不再是保存在try或catch中的那个。其实最后,因为最后必须执行,所以特别适合释放占用资源的程序的操作,比如关闭文件、网络IO等。但是,由于仍然可能被人为遗忘,Java在1.7版本中引入了新的语法,以避免忘记释放资源。

  可以自动回收资源的Try语句在处理与网络、文件等资源相关的异常时比较复杂,尤其是在处理多个资源时,比如读取文件的内容并通过网络发送出去。文件资源操作和网络资源操作过程中可能出现错误(文件找不到,权限错误,网络连接不上等。).

  在这种情况下,需要主动关闭finally块中的资源。然而,它可能会被错过,所以java 1.7后来有了try-with-resources——一个支持自动关闭资源的try语句。

  那么,如何让try语句自动帮助我们关闭异常呢?首先,这个资源应该能够支持自动关闭。只要是在Java中实现AutoCloseable接口的类的实例,就会被认为是可以自动关闭的资源。

  package com . example . learn exception;

  导入Java . io . io exception;

  公共类AutoClosableTestResource实现AutoCloseable {

  私有字符串resourceName

  私有int计数器;

  公共AutoClosableTestResource(字符串resName) {

  this.resourceName=resName

  }

  //read方法会随便读出数据或者抛出IOException。

  公共字符串read()引发IOException {

  柜台;

  if (Math.random() 0.1) {

  return 您幸运地从 resourceName 中读取了 counter 次.;

  }否则{

  抛出新的IOException(“资源不存在”);

  }

  }

  @覆盖

  公共void close()引发异常{

  system . out . println( resource released: resourceName );

  }

  }到时候try语句会帮我们自动调用close方法释放资源。接下来,让我们看看如何在try-with-resources语法中使用这个资源。

  package com . example . learn exception;

  公共类TryWithResource {

  公共静态void main(String[] args) {

  尝试(

  AutoClosableTestResource res1=new AutoClosableTestResource( res1 );

  AutoClosableTestResource res2=新的AutoClosableTestResource(res2 )

  ) {

  while (true) {

  system . out . println(res1 . read());

  system . out . println(res2 . read());

  }

  } catch(异常e) {

  e . printstacktrace();

  }

  }

  }与常规的try catch语句相比,可以自动关闭的资源是在try之后的括号中创建的。这里创建的资源可以被try代码块中的代码使用,当出现异常或正常退出时,将帮助我们释放这里的资源。

  我们执行上面的例程,我们可以看到它的输出。

  你幸运地从res1中读取了1次.

  你幸运地从res2中读取了1次.

  你幸运地读了两遍res1.

  你幸运地从res2中读了两遍.

  资源已被释放:res2。

  资源已被释放:res1。

  Java . io . io异常:资源不存在

  位于com . example . learn exception . autoclosabletestresource . read(autoclosabletestresource . Java:20)

  在程序退出之前,在at com . example . learn exception . trywithresource . main(trywithresource . Java:10)try之后的括号中创建的资源都被释放。后来我们在编写资源操作相关的代码时,尽量使用这种形式,日常开发中可以访问的资源已经实现了AutoCloseable,支持自动关闭。

  版权归作者所有:原创作品来自博主小二上九8,转载请联系作者取得转载授权,否则将追究法律责任。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: