该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。

前言:

Java的基本理念是“结构不佳的代码不能运行”。

“异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。

使用异常所带来的另一个相当明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。

异常概述:

现在我们需要编写一个五子棋程序,当用户输入下期坐标时,程序要判断用户输入是否合法,如果保证程序有较好的容错性,将会有如下的代码(伪代码):

if(用户输入包含除逗号之外的其他非数字字符)
{
alert 坐标只能是数值
goto retry
}
else if(用户输入不包含逗号) {
alert 应使用逗号分隔两个坐标值
goto retry
} else if(用户输入坐标值超出了有效范围) {
alert 用户输入坐标应位于棋盘坐标之内
goto retry
} else if(用户输入的坐标已有棋子)
{
alert 只能在没有棋子的地方下棋
goto retry
}
else {
.....
}

  

上面代码还未涉及任何有效处理,只是考虑了4种可能的错误,代码就已经急剧增加了。但实际上,上面考虑的4种情形还远未考虑到所有的可能情形(事实上,世界上的意外是不可穷举的),程序可能发生的异常情况总是大于程序员所能考虑的意外情况。

对于上面的错误处理机制,主要有以下两个缺点:

  • 无法穷举所有的异常情况。因为人类知识的限制,异常情况总比可以考虑到的情况多,总有“漏网之鱼”的异常情况,所以程序总是不够健壮。
  • 错误处理代码和业务实现代码混杂。这种错误处理和业务实现混杂的代码严重影响程序的可读性,会增加程序维护的难度。

我们希望有这样一种处理机制:

if(用户输入的数据不合法){
.....
}else{
处理逻辑
.....
}

  

上面伪码提供了一个非常强大的“if块”——程序不管输入错误的原因是什么,只要用户输入不满足要求,程序就一次处理所有的错误。这种处理方法的好处是,使得错误处理代码变得更有条理,只需在一个地方处理错误。

这就需要用到java异常了。

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

Java中的异常有以下三种类型:

检查异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

运行异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

错误:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

异常的体系结构:

我们先来统观以下Java的异常体系结构图:

Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。

只有Java语言提供了Checked异常,其他语言都没有提供Checked异常。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。

Throwable

Java异常的顶级类,所有的类都继承自Throwable

Error:

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

Exception:

Exception中异常主要分为两大类,运行时异常和检查异常。常见的运行时异常有ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、ClassNotFoundException(类型找不到),这些异常时非检查异常,程序可以选择处理,也可以不处理,编译器编译时期并不会报错。这些异常一般是由于程序逻辑错误引起的,所以建议程序员还是处理一下。除运行时异常外的所有异常我们都称为非运行时异常,也是必须处理的异常,如果不出来,程序编译会报错。

Error和Exception的区别:

ErrorException的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

检查异常:必须处理的异常

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。

非检查异常:可以不处理

包括RuntimeException及其子类和Error

不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。

异常处理机制:

Java的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。

java异常关键字:

  • try – 用于监听。try后紧跟一个花括号括起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码,当try语句块内发生异常时,异常就被抛出。【监控区域】
  • catch – 用于捕获异常。catch后对应异常类型和一个代码块,用于处理try块发生对应类型的异常。【异常处理程序】
  • finally – 用于清理资源,finally语句块总是会被执行。 多个catch块后还可以跟一个finally块,finally块用于回收在try块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。只有finally块执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。【使用finally进行清理】
  • throw – 用于抛出一个实际的异常。throw可以单独作为语句使用,抛出一个具体的异常对象。【抛出异常】
  • throws --用在方法签名中,用于声明该方法可能抛出的异常。【异常说明】

1、使用try...catch捕获异常:

语法格式:

try{
//业务实现代码
...
}catch(Exception e){
//异常处理代码
...
}

  

如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。

当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止Java程序也将退出。

下面看几个简单的异常捕获的例子:

例1:

public class DivTest {

	public static void main(String[] args) {
try {
int a=Integer.parseInt(args[0]);
int b=Integer.parseInt(args[1]);
int c=a/b;
System.out.println("您输入的两个数相除的结果是"+c);
}catch(IndexOutOfBoundsException e) {
System.out.println("数组越界,运行时参数不够");
}catch(NumberFormatException e) {
System.out.println("数字格式异常");
}catch(ArithmeticException e) {
System.out.println("算术异常");
}catch(Exception e) {
System.out.println("未知异常");
}
} }

  

  • 如果运行该程序时输入的参数不够,将会发生数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的catch块处理该异常。
  • 如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常,Java运行时将调用NumberFormatException 对应的catch块处理该异常。
  • 如果运行该程序时输入的第二个参数是0,将发生除0异常,Java运行时将调用ArithmeticException对应的catch块处理该异常。
  • 如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。

上面程序中的三种异常是我们在编程中经常遇见的,读者应该掌握这些异常。

例2:

import java.util.Date;

public class Test {

	public static void main(String[] args) {
Date d=null;
try {
System.out.println(d.after(new Date()));
}catch(NullPointerException e) {
System.out.println("空指针异常");
}catch(Exception e) {
System.out.println("未知异常");
}
} }

  

上面程序针对NullPointerException异常提供了专门的异常处理块。上面程序调用一个null对象的after0方法,这将引发NullPointerException异常(当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常),Java 运行时将会调用NullPointerException对应的catch块来处理该异常;如果程序遇到其他异常,Java运行时将会调用最后的catch块来处理异常。

catch块处理异常遵循着:先小后大,即先子类后父类。正如在前面程序所看到的,程序总是把对应Exception类的catch块放在最后,这是为什么呢?读者可能明白原因:如果把Exception类对应的catch块排在其他catch块的前面,Java运行时将直接进入该catch块(因为所有的异常对象都是Exception或其子类的实例),而排在它后面的catch块将永远也不会获得执行的机会。

多异常捕获:

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

上面的例1可以改成如下代码实现:

public class Test {

	public static void main(String[] args) {
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("数组越界,数字格式异常,算术异常");
}catch(Exception e) {
System.out.println("未知异常");
}
} }

  

2、使用throws声明抛出异常:

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

import java.io.FileInputStream;
import java.io.FileNotFoundException; public class Test { public static void main(String[] args) throws FileNotFoundException {
FileInputStream fis=new FileInputStream("a.txt");
} }

  

上面程序声明不处理IOException异常,将该异常交给JVM处理,所以程序一旦遇到该异常,JVM就会打印该异常的跟踪栈信息,并结束程序。运行上面程序,会看到如下图所示的运行结果。

3、使用throw抛出异常:

public class Test {

	public static void main(String[] args) throws Exception {
try {
int a=Integer.parseInt(args[0]);
int b=Integer.parseInt(args[1]);
int c=a/b;
if(b==0) {
throw new Exception("除数不能为0");
}
System.out.println("您输入的两个数相除的结果是"+c);
}catch(Exception e) {
System.out.println("未知异常");
}
} }

  

上面程序中粗体字代码使用throw语句来自行抛出异常。当Java运行时接收到开发者自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应的catch块,由该catch块来处理该异常。也就是说,不管是系统自动抛出的异常,还是程序员手动抛出的异常,Java运行时环境对异常的处理没有任何差别。

4、访问异常信息:

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

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

getMessage():返回该异常信息的跟踪栈信息输出到标准错误输出

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

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

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

5、使用finally回收资源:

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

在哪里回收这些物理资源呢?在try块里回收?还是在catch块中进行回收?假设程序在try块里进行资源回收,根据图10.1所示的异常捕获流程—一如果try块的某条语句引起了异常,该语句后的其他语句通常不会获得执行的机会,这将导致位于该语句之后的资源回收语句得不到执行。如果在catch块里进行资源回收,但catch块完全有可能得不到执行,这将导致不能及时回收这些物理资源。

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

try{
//业务实现代码
...
}catch(SubException e){
//异常处理块
...
}catch(SubException e2){
//异常处理块
...
}finally{
//资源回收
...
}

  

例如:

public class Test {

	public static void main(String[] args) throws Exception {
try {
int a=10;
int b=0;
int c=a/b;
}catch(Exception e) {
System.out.println("未知异常");
}finally {
System.out.println("资源回收");
}
} }

  

结果:

未知异常
资源回收

  

总结:

Java——深入理解Java异常体系的更多相关文章

  1. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  2. Java深度理解——Java字节代码的操纵

    导读:Java作为业界应用最为广泛的语言之一,深得众多软件厂商和开发者的推崇,更是被包括Oracle在内的众多JCP成员积极地推动发展.但是对于 Java语言的深度理解和运用,毕竟是很少会有人涉及的话 ...

  3. 一个人java深入理解java logback配置

    http://blog.csdn.net/initphp/article/category/1230072/2

  4. 夯实Java基础系列10:深入理解Java中的异常体系

    目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...

  5. Java基础系列5:深入理解Java异常体系

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 前言: Java的基 ...

  6. 牛客网Java刷题知识点之什么是异常、异常处理的原理是什么、为什么要使用异常、异常体系、运行时异常、普通异常、自定义异常、异常链

    不多说,直接上干货! 在这个世界不可能存在完美的东西,不管完美的思维有多么缜密,细心,我们都不可能考虑所有的因素,这就是所谓的智者千虑必有一失.同样的道理,计算机的世界也是不完美的,异常情况随时都会发 ...

  7. 深入浅出 Java Concurrency (36): 线程池 part 9 并发操作异常体系[转]

    并发包引入的工具类很多方法都会抛出一定的异常,这些异常描述了任务在线程池中执行时发生的例外情况,而通常这些例外需要应用程序进行捕捉和处理. 例如在Future接口中有如下一个API: java.uti ...

  8. 《深入理解Java虚拟机》Java内存区域与内存溢出异常

    注:“蓝色加粗字体”为书本原语 先来一张JVM运行时数据区域图,再接下来一一分析各区域功能:   程序计数器 程序计数器(program Counter Register)是一块较小的内存空间,它可以 ...

  9. JAVA异常体系

    1.异常体系 ----|Throwable 所有错误或异常的父类 --------|Error(错误) --------|Exception(异常)一般能通过代码处理 ------------|运行时 ...

随机推荐

  1. winform怎么实现财务上凭证录入和打印

    序言 现如今存在的财务软件层出不穷,怎么样让自己的业务系统与财务系统相结合,往往是很多公司头痛的问题.大多数公司也没有这个能力都去开发一套属于自己的财务软件,所以只有对接像金蝶用友这类的财务软件,花费 ...

  2. hive常用函数一

    Hive概念 Hive最适合于数据仓库应用程序,使用该应用程序进行相关静态数据分析,不需要快速响应出结果,而数据本身不会发生频繁变化. Hdfs分布式文件系统限制了hive,使其不支持记录级别的更新. ...

  3. 【Android】EventReminder使用教程(日历事件导出封装库)

    碎碎念 为啥要写这个库呢? 尝试自己写一个库调用,学习一下这个流程,为以后做准备 日历库在网上的资料太少了,而这个功能却又很实用 自己做的项目都会涉及到事件导出功能,不想重复写代码 使用方法 引入 在 ...

  4. Map使用foreach遍历方式,Map获取第一个键值

    List<Map<String, Object>> mapList = new ArrayList<>();  for (Map.Entry<String,O ...

  5. GeoGebra重复手段实现

    1.自定义工具部分可以在网上搜一些别人做的工具,主要是把自己经常做的一些任务做成工具,减少重复过程 2.列表部分的简单操作如图所示,实现对三个点的多项式拟合 3.通过序列指令格式可以做一个好玩的效果, ...

  6. 面试官:兄弟,说说Java到底是值传递还是引用传递

    二哥,好久没更新面试官系列的文章了啊,真的是把我等着急了,所以特意过来催催.我最近一段时间在找工作,能从二哥的文章中学到一点就多一点信心啊! 说句实在话,离读者 trust you 发给我这段信息已经 ...

  7. [YII2] 去除自带头部以及底部右下角debug调试功能

    YII2 去除自带头部以及底部右下角debug调试功能

  8. IO多路复用小故事

    背景故事 小王住在某城市, 生活并长大. 最近, 小城引进了一个企业, 邮局. 这个邮局可了不得, 只要你花上几角钱, 就可以将一封信送到千里之外的朋友手中. 小王也趁机体验了一把, 得劲. 这天, ...

  9. 它来了!它来了!Seata Go Client 它来了!!!

    抱歉抱歉,这个标题一看就是个很标题党的标题.本文所述的 Seata Go Client 只支持 TCC 模式,并不像 Java 版的能支持到 AT 模式.SAGA 模式.XA 模式,聊胜于无.说到这里 ...

  10. SQL三表连接查询与集合的并、交、差运算查询

    use db_sqlserver2 select 姓名, 工资, 面积, 金额, (工资+金额/1000) as 实发工资 from 职工,仓库, 订购单 where 职工.职工号=订购单.职工号 a ...