该系列博文会告诉你如何从入门到进阶,一步步地学习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. 超过百万的StackOverflow Flutter 问题

    老孟导读:今天分享StackOverflow上高访问量的20大问题,这些问题给我一种特别熟悉的感觉,我想你一定或多或少的遇到过,有的问题在stackoverflow上有几十万的阅读量,说明很多人都遇到 ...

  2. Python pip高级用法

    1.pip 高级用法为了便于用户安装和管理第三方库和软件,越来越多的编程语言拥有自己的包管理工 具,如 nodejs 的 npm, ruby 的 gem. Python 也不例外,现在 Python ...

  3. PHP 语法引用使用及实现

    说明 这里基于 php7.2.5 进行测试,php7 之后内部结构变化应该不是太大,但与 php5.X 有差别. 什么是引用 在 PHP 中引用是一种数据类型 (结构),是指 指向同一个类型的数据结构 ...

  4. 【高并发】不废话,言简意赅介绍BlockingQueue

    写在前面 最近,有不少网友留言提问:在Java的并发编程中,有个BlockingQueue,它是个阻塞队列,为何要在并发编程里使用BlockingQueue呢?好吧,今天,就临时说一下Blocking ...

  5. AJ学IOS(05)UI之360等下载管理器九宫格UI

    AJ分享,必须精品 先看效果 主要是完成了九宫格UI的搭建 代码 - (void)viewDidLoad { [super viewDidLoad]; //九宫格中每个格子的宽 #define kAp ...

  6. Linux终端命令格式

    01.终端命令格式 command [-options] [parameter] 说明: command:命令名,响应功能的英文单词或单词的缩写 [-options]:选项,可用来对命令进行控制,也可 ...

  7. 时间格式的转化 vue与js 年月日 时分秒

    首先使用原生转化的方法 第一种 //时间转换 dateStr(d, sign) { //如果没有传递符号,给一个默认的符号 if (!sign) { sign = '-' } //获取d里面年月日时分 ...

  8. Python爬取抖音高颜值小视频

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 有趣的python PS:如有需要Python学习资料的小伙伴可以加 ...

  9. flutter和react native如何选择

    [关于性能]跨平台开发第一个考虑的就是性能问题RN的效率由于是将View编译成了原生View,所以效率上要比基于Cordova的HTML5高很多,但是它也有效率问题,RN的渲染机制是基于前端框架的考虑 ...

  10. pytorch seq2seq闲聊机器人

    cut_sentence.py """ 实现句子的分词 注意点: 1. 实现单个字分词 2. 实现按照词语分词 2.1 加载词典 3. 使用停用词 "" ...