(一) 什么是异常(程序没有语法错误, 可能产生的运行时错误)
比如你使用java程序开发了一个计算器,可以让用户进行计算,但是在计算除法的过程中(程序运行过程中),用户把除数设为0, 这时我们的程序执行就会出错(大家都读过小学,知道除法中,除数不能为0),即抛出异常。异常情况是指程序在运行时,可能由与外部系统的条件变更(与我们一厢情愿所设想的不一设)而导致程序可能会出错的情况,如我们的代码要连结数据库,但数据库未启动,要创建目录,操作系统上却己存在同名的真实文件;即所谓异常是指可能(仅是可能)由与外部系统的,导致程序可能出错(中断运行)的原因。
代码如下:
import java.util.Scanner;
/**
* 演示除数为0的情况
* @author pactera
*
*/
public class TestException {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println("计算结果如下:");
System.out.println(num1+"/"+num2+"="+(num1/num2));
}
}
当我们除数不是0的时候,程序是正常执行的:
但是当我们的除数输入的是0,程序就会出现错误,也就是我们说的异常:
(二)异常的体系结构
java为我们提供了非常完美的异常处理机制,使得我们可以更加专心于我们的程序,在使用异常之前我们需要了解它的体系结构:
从上面这幅图可以看出,Throwable是java语言中所有错误和异常的超类(万物即可抛)。它有两个子类:Error、Exception。
其中Error为错误,是程序无法处理的,如OutOfMemoryError、ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过JVM在大多数情况下会选择终止线程。
而Exception是程序可以处理的异常。它又分为两种CheckedException(受捡异常),一种是UncheckedException(不受检异常)。其中CheckException发生在编译阶段,必须要使用try…catch(或者throws)否则编译不通过。而UncheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的,难以排查,我们一般都需要纵观全局才能够发现这类的异常错误,所以在程序设计中我们需要认真考虑,好好写代码,尽量处理异常,即使产生了异常,也能尽量保证程序朝着有利方向发展。
(三) 优雅的异常处理
Java具有代码级的强制性异常检测机制,即许多常见的可预料的异常可能都必须编写代码处理,否则就无法编译通过。
3.1 try-catch
网上流传这样一个笑话:世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。对于初学者来说处理异常就是try…catch, try…catch确实是用的最多也是最实用的。
如果一段代码可能会抛出异常---编译器检测或程序员认为会,就需要将这些代码放在try catch块中,如下代码示例;这很好理解:try指“尝试”执行可能出现异常的代码,如果成功,则乎略备用方案,即(B)区的代码;但如失败,代码会catch(捕获)到一个异常对象,放弃(A)计划,开始执行(B)计划!
try{
可能抛出异常的代码. . .
//如果没有问题出理,执行如下面的代码
(A)其它计算代码…
}catch(Exception ef){
//如果出现异常后执行的代码:
(B)出了异常情况的计算代码. . .
}
try catch结构的异常处理提供了这样一种机制:如果代码执行成功,程序流程正常,(B)块的代码将不会执行;如果执行时(A)代码前的语句出现异常,(A)代码将不会执行,程序跳转到(B)代码块开始执行,同时,可(B)代码块中可以得到Exception类型变量ef对这个异常对象的引用,可以调用ef. printStackTrace();方法打印出异常的详细细信息;这为程序从错误中恢复提供了可行的手段。
import java.util.Scanner;
/**
* 演示除数为0的情况
* @author pactera
*
*/
public class TestException {
public static void main(String[] args) {
try{//放的是可能会出现异常的代码
Scanner input = new Scanner(System.in);
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println("计算结果如下:");
System.out.println(num1+"/"+num2+"="+(num1/num2));
System.out.println("========程序结束!!!!=============");
}catch(ArithmeticException e){ //catch是捕获异常,并进行处理
//需要注意,我们这个catch捕获的只能是ArithmeticException异常或者其子类异常,对其他异常不捕获
e.printStackTrace();//可以在控制台打印详细的异常信息,在开发阶段使用,便于调试代码
//异常的处理,我们简单输出一句话,但是在真实的项目中,不能只是输出一句话,而是要做相应的处理
System.out.println("你丫的没读过小学吗?不知道除数不能为0吗");
}
System.out.println("============程序结束!!!!==================");
}
}
代码运行情况:
第一种情况,当程序正常执行,try中的代码执行完,catch中的代码不会执行,catch后面的代码会执行
第二种情况,当程序发生异常,并且异常的类型与catch小括号的异常一样,try中发生异常处之后的代码不会执行,则执行catch中的代码,catch后的代码也会执行.
第三种情况,当程序发生异常, 并且异常的类型与catch小括号的异常不一样, try中发生异常处之后的代码不会执行,也不执行catch中的代码,catch后的代码也不执行.程序会在发生异常区终止程序.
需要注意的是:
1.try catch块中变量做用域:try块中定义的变量符合我们前面所讲的变量做用范围的规则,即变量只能在限定自己最近的一对大括号内使用;即try catch块内一对大括号中定义的变量不能在后面的代码块内使用;
2.方法返回值:如果方法有定义的返回值,这个方法就有可以在正常执行时有一个返回值,或在catch到异常时有个返回值---不能仅仅只在try块中return一个返回值。
3.要注意的是,并不是所有的异常都强制需要try catch,在java中,异常分为强制检测和非强制检测二种:
非强制检测在编译时,不需要try catch,但如因程序员编码时逻辑错误,在运行时就会抛出,如下代码:
int[] ia=new int[10];
ia[10]=100;
这段代码在运行时,就会抛出Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10 ,即数组越界的异常。
再如:
String s="123abc";
int sa=Integer.parseInt(s);//转为int型
运行时会抛出:
java.lang.NumberFormatException: For input string: "123abc"
因为要将s解析为int时,s中的字符必须是0~9之间的。
4.如果某段代码可以回出现多个异常,那么我们可以使用一个try后面接多个catch来进行处理,如果多个异常有继承关系,那么我们应该先写子类,再写父类
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* 演示除数为0的情况
* @author pactera
*
*/
public class TestException {
public static void main(String[] args) {
try{//放的是可能会出现异常的代码
Scanner input = new Scanner(System.in);
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println("计算结果如下:");
System.out.println(num1+"/"+num2+"="+(num1/num2));
System.out.println("======代码正常执行完==========");
}catch(ArithmeticException e){ //catch是捕获异常,并进行处理
//需要注意,我们这个catch捕获的只能是ArithmeticException异常或者其子类异常,对其他异常不捕获
e.printStackTrace();//可以在控制台打印详细的异常信息,在开发阶段使用,便于调试代码
//异常的处理,我们简单输出一句话,但是在真实的项目中,不能只是输出一句话,而是要做相应的处理
System.out.println("你丫的没读过小学吗?不知道除数不能为0吗");
}catch(InputMismatchException e){
System.out.println("你丫不知道什么是数字吗,乱输个啥");
}catch(Exception e){ //Exception是所有异常类的父类,所以要写在最后
System.out.println("哎,我也不知道怎么办了");
}
System.out.println("===========程序结束!!!!================");
}
}
3.2 try-catch-finally
对于上面的代码,我们再来做一点改进,我想让上面的代码的”程序结束”这句话不管是否发生异常,而且不管是否catch到相同的异常,我都要输出,这是很有必要的,就比如说你开车,如果正常到达,你是不是也要熄火啊,当如果你车抛锚了,也就是出现异常了,那你是不是也要熄火,因为不熄火,就浪费资源了.同样在java中有一些东西也要必须关闭,比如后面需要学到的IO流,数据库连接,这些都是计算机的资源,所以不管是在程序是否正常执行,都必须关闭.
那这些必须关闭的资源应该放在那里呢?放在try中,不行吧,如果出现异常,那try中的代码就不会执行了,如果放在catch中,那如果ctach捕获的异常不一样,那也不会执行,所以java就给我们准备了这样一个东西,这就是finally块,finally块表示程序不管是否发生异常,总会执行.现在我们把上面的代码进行优化:
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* 演示除数为0的情况
*
* @author pactera
*
*/
public class TestException {
public static void main(String[] args) {
try {// 放的是可能会出现异常的代码
Scanner input = new Scanner(System.in);
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println("计算结果如下:");
System.out.println(num1 + "/" + num2 + "=" + (num1 / num2));
System.out.println("======代码正常执行完==========");
} catch (ArithmeticException e) { // catch是捕获异常,并进行处理
// 需要注意,我们这个catch捕获的只能是ArithmeticException异常或者其子类异常,对其他异常不捕获
e.printStackTrace();// 可以在控制台打印详细的异常信息,在开发阶段使用,便于调试代码
// 异常的处理,我们简单输出一句话,但是在真实的项目中,不能只是输出一句话,而是要做相应的处理
System.out.println("你丫的没读过小学吗?不知道除数不能为0吗");
} catch (InputMismatchException e) {
System.out.println("你丫不知道什么是数字吗,乱输个啥");
} catch (Exception e) { // Exception是所有异常类的父类,所以要写在最后
System.out.println("哎,我也不知道怎么办了");
} finally {//不管是否异常都会执行
System.out.println("================程序结束!!!!=====================");
}
}
}
注意:
1.try 代码段包含的是可能产生异常的代码
2.try 代码段后跟一个或多个catch代码段。(或跟一个finally代码段)
3.在jdk1.7之前每个catch代码段只声明一种其能处理的特定类型的异常,并提供处理的方法。 Jdk1.7之后,一个catch代码可以可以声明多个能处理的特定异常的类型,多个类型之间用”|”隔开
例如:
catch(ExceptionName1 e | ExceptionName1 e){
...... //异常的处理代码
}
4.当异常发生时,程序会中止当前的流程去执行相应的catch代码段。
5.写catch代码时,先捕获的异常的范围不能大于后捕获的异常的范围。
6.finally段的代码无论是否发生异常都执行。
3.3 throw 与throws
通过学习try-catch-finally,我们知道自己来处理异常,但是有一些异常,在我们编写的这个方法中,不知道怎么处理,那怎么办呢?
大家换个思维想一想,我处理不了的异常,那就把这个异常抛给别人(也就是调用我这个方法)的来处理,这也有点想我们所说的推卸责任.那怎么抛出异常呢? 就使用我们的throw 来抛出异常,但是你抛出了异常,是不是要告诉调用者,说我这个方法可能会抛出那些异常呢?我们推卸了责任,那也要让背黑锅的人知道他背的是什么黑锅,不能做得太绝,是吧.那怎么在方法上声明可能抛出的异常呢,就使用throws.
throw表示抛出异常,语法是:
throw new 异常类型([异常信息]);
比如说: throw new Exception(“抛个异常玩玩”);
throws表示用来声明方法可能会抛出那些异常: 语法是:
throws 异常类型1,异常类型2…
代码演示:
//定义一个除法的方法,并声明异常
public int division(int num1 ,int num2) throws ArithmeticException{
if(num2 == 0){
//抛出异常,如果抛出异常,则抛出异常后面的代码不会执行
throw new ArithmeticException("除数不能为0");
}
return num1/num2;
}
细节:
1.如果throw 异常对象; 如果异常对象类型是编译时异常(非RuntimeException异常), 一定在方法上使用throws 声明
2.如果throw 异常对象, 如果异常对象类型是运算时异常(RuntimeException异常或者子类), 方法上可以使用throws声明, 也可以不声明
3.如果方法调用其他代码(方法),其他代码,给我们抛了一个异常,
第一种方式: 使用try-catch处理异常
第二种方式: 在方法上使用throws 异常类型, 把这个异常继续往上抛, 异常可以一直往上抛, 都不处理, 只能交给jvm处理, jvm处理方式: 中断代码执行, 直接在控制台输出异常堆栈信息
(四)异常信息的分析与程序调试:
事实上,一个软件与其说是编写出来,不如说是调试出来的;我们通常大量的使用System.out.println…插入在程序中,以输出当时变量值或其它信息,供我们判断程序的执行情况,这是一种最常用的方法。
当程序出现异常进,通过调用Exception对象的printStackTrace()方法可以获取更详尽的出错信息:由上可以看出,这个异常是在我们的FileTest类的testCreateAndDelete方法的第52行代码抛出的, 即”temFile.createNewFile();”这行代码,只要根据方法的调用向上回溯,并根据异常描述信息,我们很会就会发现引起文件不能创建的原因。因此,异常确实是程序员的好朋友,向你坦白报告错误详情;提高编程能力的一个很重要的方法就是仔细观察异常信息详情
异常信息会沿着方法调用的路径向回传递,直到我们程序的入口点main方法,像上面打印出的异常信息输出的层级结构;异常向上传递如下图示:
(五)自定义异常
5.1 创建异常类
创建自定义异常,需要继承Exception 或其子类。习惯上包装一下父类的构造方法。
public class MyException extends Exception {
public MyException() {
super();
}
public MyException(String msg) {
super(msg);
}
public MyException(Throwable cause) {
super(cause);
}
public MyException(String msg, Throwable cause) {
super(msg, cause);
}
}
5.2 使用自定义异常类
根据我们业务需要,对某些情况, 也希望有对应的异常类, 但是jdk提供异常没有这种类型,
我们可以自己定义一个异常类,
步骤:
1.编写一个类, 规范: 异常类类名: xxxException
2.类继承java.lang.Exception或者子类
3.根据父类的构造方法生成本类的构造方法
- 在业务代码中,使用throw new 自定义异常类()
public void fun() throws MyException {
if(true){
throw new MyException("测试一下...");
}
}
public String[] createArray(int length) throws MyException {
if (length < 0) {
throw new MyException("数组长度小于0,不合法");
}
return new String[length];
}
5.3异常常用的方法
getMessage() 获取异常的错误信息
printStackTrace() 打印异常的堆栈信息
堆栈信息查看: