Java异常的深入研究与分析

Java异常的深入研究与分析

大家好,又见面了,我是全栈君。

前言

本文是异常内容的集大成者,力求全面,深入的异常知识研究与分析。本文由金丝燕网独家撰写,参考众多网上资源,经过内容辨别取舍,文字格式校验等步骤编辑而成,以飨读者。对于本文的内容,建议小白需要多多思考力求掌握,对于老手只需意会温故知新。对于本文的内容,属于基础知识研究范畴,切勿以为读完此文就能将异常知识掌握到家。切记:操千曲而后晓声,观千剑而后识器,所以我觉得没有大量的源码阅读经验,你很难知道什么时候需要自定义异常,什么时候需要抛出异常。

异常机制概述

异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。

异常处理的流程

当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,如果找到,将控制权交到catch块中的代码,然后继续往下执行程序,try块中发生异常的代码不会被重新执行。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被中止。

异常的结构

异常的继承结构:Throwable为基类,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception。Error和RuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)。

Java异常的深入研究与分析

Error异常

Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA 虚拟机出现错误。Error是一种unchecked Exception,编译器不会检查Error是否被处理,在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。

RuntimeException异常

Exception异常包括RuntimeException异常和其他非RuntimeException的异常。RuntimeException 是一种Unchecked Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。

Checked Exception异常

Checked Exception异常,这也是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是checked Exception,上图中的IOException和ClassNotFoundException。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。

在声明方法时候抛出异常

语法:throws(略)

为什么要在声明方法抛出异常?

方法是否抛出异常与方法返回值的类型一样重要。假设方法抛出异常却没有声明该方法将抛出异常,那么客户程序员可以调用这个方法而且不用编写处理异常的代码。那么,一旦出现异常,那么这个异常就没有合适的异常控制器来解决。

为什么抛出的异常一定是已检查异常?RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改;只有已检查异常才是程序员所关心的,程序应该且仅应该抛出或处理已检查异常。而已检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数;客户程序员自己使用throw语句抛出异常。

注意:覆盖父类某方法的子类方法不能抛出比父类方法更多的异常,所以,有时设计父类的方法时会声明抛出异常,但实际的实现方法的代码却并不抛出异常,这样做的目的就是为了方便子类方法覆盖父类方法时可以抛出异常。

在方法中如何抛出异常

语法:throw(略)抛出什么异常?

对于一个异常对象,真正有用的信息是异常的对象类型,而异常对象本身毫无意义。比如一个异常对象的类型是ClassCastException,那么这个类名就是唯一有用的信息。所以,在选择抛出什么异常时,最关键的就是选择异常的类名能够明确说明异常情况的类。

异常对象通常有两种构造函数:一种是无参数的构造函数;另一种是带一个字符串的构造函数,这个字符串将作为这个异常对象除了类型名以外的额外说明。

为什么要创建自己的异常?

当Java内置的异常都不能明确的说明异常情况的时候,需要创建自己的异常。需要注意的是,唯一有用的就是类型名这个信息,所以不要在异常类的设计上花费精力。

throw和throws的区别

 
  1. public class TestThrow 
  2.  
  3.     public static void main(String[] args) 
  4.     { 
  5.         try 
  6.         { 
  7.             //调用带throws声明的方法,必须显式捕获该异常 
  8.             //否则,必须在main方法中再次声明抛出 
  9.             throwChecked(-3);             
  10.         } 
  11.         catch (Exception e) 
  12.         { 
  13.             System.out.println(e.getMessage()); 
  14.         } 
  15.         //调用抛出Runtime异常的方法既可以显式捕获该异常, 
  16.         //也可不理会该异常 
  17.         throwRuntime(3); 
  18.     } 
  19.     public static void throwChecked(int a)throws Exception 
  20.     { 
  21.         if (a > 0) 
  22.         { 
  23.             //自行抛出Exception异常 
  24.             //该代码必须处于try块里,或处于带throws声明的方法中 
  25.             throw new Exception(“a的值大于0,不符合要求”); 
  26.         } 
  27.     } 
  28.     public static void throwRuntime(int a) 
  29.     { 
  30.         if (a > 0) 
  31.         { 
  32.             //自行抛出RuntimeException异常,既可以显式捕获该异常 
  33.             //也可完全不理会该异常,把该异常交给该方法调用者处理 
  34.             throw new RuntimeException(“a的值大于0,不符合要求”); 
  35.         } 
  36.     } 
  37. }  

补充:throwChecked函数的另外一种写法如下所示:

 
  1. public static void throwChecked(int a) 
  2.     { 
  3.         if (a > 0) 
  4.         { 
  5.             //自行抛出Exception异常 
  6.             //该代码必须处于try块里,或处于带throws声明的方法中 
  7.             try 
  8.             { 
  9.                 throw new Exception(“a的值大于0,不符合要求”); 
  10.             } 
  11.             catch (Exception e) 
  12.             { 
  13.                 // TODO Auto-generated catch block 
  14.                 e.printStackTrace(); 
  15.             } 
  16.         } 
  17.     }  

注意:此时在main函数里面throwChecked就不用try异常了。

应该在声明方法抛出异常还是在方法中捕获异常?

处理原则:捕捉并处理哪些知道如何处理的异常,而传递哪些不知道如何处理的异常

使用finally块释放资源

finally关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。在以下三种情况下会进入finally块:

(1) try块中的代码正常执行完毕。

(2) 在try块中抛出异常。

(3) 在try块中执行return、break、continue。

因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些代码放入finally块中。当你的程序中使用了外界资源,如数据库连接,文件等,必须将释放这些资源的代码写入finally块中。

必须注意的是:在finally块中不能抛出异常。JAVA异常处理机制保证无论在任何情况下必须先执行finally块然后再离开try块,因此在try块中发生异常的时候,JAVA虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再向外抛出异常。如果在finally块中抛出异常,try块捕捉的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try块中发生的真正的异常堆栈信息则丢失了。请看下面的代码:

 
  1. Connection  con = null
  2. try 
  3.     con = dataSource.getConnection(); 
  4.     …… 
  5. catch(SQLException e) 
  6.     …… 
  7.     throw e;//进行一些处理后再将数据库异常抛出给调用者处理 
  8. finally 
  9.     try 
  10.     { 
  11.         con.close(); 
  12.     } 
  13.     catch(SQLException e) 
  14.     e.printStackTrace(); 
  15.     …… 
  16. }  

运行程序后,调用者得到的信息如下

java.lang.NullPointerException

at myPackage.MyClass.method1(methodl.java:266)

而不是我们期望得到的数据库异常。这是因为这里的con是null的关系,在finally语句中抛出了NullPointerException,在finally块中增加对con是否为null的判断可以避免产生这种情况。

丢失的异常

请看下面的代码:

 
  1. public void method2() 
  2. try 
  3.     …… 
  4.     method1();  //method1进行了数据库操作 
  5. catch(SQLException e) 
  6.     …… 
  7.     throw new MyException(“发生了数据库异常:”+e.getMessage); 
  8. public void method3() 
  9.     try 
  10.     method2(); 
  11. catch(MyException e) 
  12.     e.printStackTrace(); 
  13.     …… 
  14. }  

上面method2的代码中,try块捕获method1抛出的数据库异常SQLException后,抛出了新的自定义异常MyException。这段代码是否并没有什么问题,但看一下控制台的输出:

 
  1. MyException:发生了数据库异常:对象名称‘MyTable’ 无效。 
  2.  at MyClass.method2(MyClass.java:232) 
  3.  at MyClass.method3(MyClass.java:255)  

原始异常SQLException的信息丢失了,这里只能看到method2里面定义的MyException的堆栈情况;而method1中发生的数据库异常的堆栈则看不到,如何排错呢,只有在method1的代码行中一行行去寻找数据库操作语句了。

JDK的开发者们也意识到了这个情况,在JDK1.4.1中,Throwable类增加了两个构造方法,public Throwable(Throwable cause)和public Throwable(String message,Throwable cause),在构造函数中传入的原始异常堆栈信息将会在printStackTrace方法中打印出来。但对于还在使用JDK1.3的程序员,就只能自己实现打印原始异常堆栈信息的功能了。实现过程也很简单,只需要在自定义的异常类中增加一个原始异常字段,在构造函数中传入原始异常,然后重载printStackTrace方法,首先调用类中保存的原始异常的printStackTrace方法,然后再调用super.printStackTrace方法就可以打印出原始异常信息了。可以这样定义前面代码中出现的MyException类:

 
  1. import java.io.PrintStream; 
  2. import java.io.PrintWriter; 
  3. public class MyException extends Exception 
  4.  
  5.     private static final long serialVersionUID = 1L; 
  6.     //原始异常 
  7.     private Throwable cause; 
  8.     //构造函数 
  9.     public MyException(Throwable cause) 
  10.     { 
  11.         this.cause = cause; 
  12.     } 
  13.     public MyException(String s,Throwable cause) 
  14.     { 
  15.         super(s); 
  16.         this.cause = cause; 
  17.     } 
  18.     //重载printStackTrace方法,打印出原始异常堆栈信息 
  19.     public void printStackTrace() 
  20.     { 
  21.         if (cause != null
  22.         { 
  23.             cause.printStackTrace(); 
  24.         } 
  25.         super.printStackTrace(); 
  26.     } 
  27.  
  28.     public void printStackTrace(PrintStream s) 
  29.     { 
  30.         if (cause != null
  31.         { 
  32.             cause.printStackTrace(s); 
  33.         } 
  34.         super.printStackTrace(s); 
  35.     } 
  36.  
  37.     public void printStackTrace(PrintWriter s) 
  38.     { 
  39.         if (cause != null
  40.         { 
  41.             cause.printStackTrace(s); 
  42.         } 
  43.         super.printStackTrace(s); 
  44.     } 
  45. }  

作者:佚名

来源:51CTO

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/108289.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • eclipse中各种代码的注释快捷键

    eclipse中各种代码的注释快捷键Java文件:注释和取消注释的快捷键都是:CTRL+/或Shift+Ctrl+CJS文件:注释和取消注释的快捷键都是:CTRL+/或Shift+Ctrl+Cxml文件:注释:选中要注销的代码CTRL+SHIFT+/或选中代码按Shift+Ctrl+C取消注释:CTRL+SHIFT+\或Shift+Ctrl+Cjsp文件:ht…

  • ssm框架过时了吗_ssm和mvc框架

    ssm框架过时了吗_ssm和mvc框架日志如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手曾经:sout,debug现在:日志工厂掌握STDOUT_LOGGINGLOG4Jlog4j什么是Log4j?我们可以控制日志信息输送的目的地是控制台我们也可以控制每一条日志的输出格式通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。分页减少数据量selsect * from user limit startIndex,pageS

  • paceMaker_pacemaker怎么读

    paceMaker_pacemaker怎么读1. 简介 Pacemaker是一个集群资源管理者。他用资源级别的监测和恢复来保证集群服务(aka.资源)的最大可用性。它可以用你所擅长的基础组件(Corosync或者是Heartbeat)来实现通信和关系管理。​2. 特性 Pacemaker包含以下的关键特性:  监测并恢复节点和服务级别的故障​  存储无关,并不需要共享存储​  资源无关,任何能用脚本控制的资源

    2022年10月25日
  • c# taskscheduler使用场合_hbase shell put

    c# taskscheduler使用场合_hbase shell put这里记录下TaskScheduler的简单用法。使用场景:在使用Task的时候,大家都知道用TaskFactory.StartNew可以用来创建一个Task。这里如果创建10个,那么这10个Task就各自放飞直接运行了。一般情况下是没什么大问题,如果这10个中的每个Task非常耗CPU或者内存,而公司的产品又是非常考验配置成本(比如一体机,移动设备等),就需要让这10个Task按照一定要求执行,比如串行执行,从而节省资源、让机器还可以顺畅去干别的事情。Task…

    2022年10月11日
  • 电商网站的搭建研究报告_连连跨境电商网站构建

    电商网站的搭建研究报告_连连跨境电商网站构建电商网站的搭建研究Researchontheconstructionofe-commercewebsite摘要互联网给全世界提供了最强大的网络平台,在互联网上不仅能同时快速传递大量的信息(数据、文件),而且还实现了网络营销、电子支付,提供各种新型的服务。经济全球化的发展态势和全球经济贸易的规模发展迫切需要一种新型的经济运作模式和商业运营模式。随着互联网的普及,还有各大电商平…

  • Windows连接树莓派_树莓派4 win10专业版

    Windows连接树莓派_树莓派4 win10专业版一、准备工作树莓派3B+主板一个(系统已烧制完毕)win10电脑一台网线一条二、win10设置1.将网线与树莓派连接2.右击网络连接图标,选择打开“网络和Internet”设置3.打开网络和共享中心4.打开更改适配器设置5.选择以太网2,右击属性进入,对共享选项卡进行设置,勾选允许,并选择以太网三、查看树莓派IP通过快捷键win+R进入命令行界面…

    2022年10月14日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号