一、异常
1、异常的概述
1)异常:就是程序在运行时出现不正常情况。
2)异常由来:问题也是现实生活中一个具体的事物,也可以通过java类的形式进行描述。并封装成对象。其实就是java对不正常情况进行描述后的对象体现。
2、异常的体系
对于问题的划分有两种:一种是严重的问题,java通过Error类对其进行描述,对于Error一般不编写针对性代码进行处理;一种是非严重的问题,java通过Exception类对其进行描述,对于Exception可以使用针对性的处理方式进行处理。无论Error或者Exception都具有一些共性内容。比如:不正常情况的信息,引发原因等。将它们向上提取,可以得到Java中所以异常的父类Throwable。
Throwable
|–Error
|–Exception
|–RuntimeException//特殊异常类,抛时不需要声明
|–其他异常类
异常体系的特点:
1、异常体系中的所有类以及建立的对象都具备可抛性。
2、也就是说可以被throw和throws关键字所操作。
3、只有异常体系具备这个特点
3、异常的分类
unchecked Exception(未检查异常):任何Error的子类以及RuntimeException的子类都称为未检查异常,编译时不会报错。
checked Exception(已检查异常):其他的异常都被称为已检查异常。该异常如果没有被处理(没有抛也,没有try),编译时会报错。
所有运行时异常均继承自RuntimeException类,常见的有:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
4、声明并抛出异常
Java中使用throws关键字用来声明异常,自己并不处理异常,而是将该异常传递给调用者,让调用者去处理这个异常。throw关键字用来抛出异常。throws用在方法头上,后面跟的是异常的类;throw用在方法体中,后面跟的是一个异常的对象。
1)声明异常
Java方法使用throws关键字在其方法头中声明可能抛出的异常。例如,Java中的BufferrdReader类中的readLine方法。
public String readLine() throws IOException
该方法头表明方法会返回一个字符串,同时也可能抛出一个IOException异常对象。如果一个方法要抛出多个异常,可以用逗号将它们分开。
public void method() throws IOException,MalformedURIException
什么时候需要在方法头中使用throws关键字呢?
a、调用了一个会抛出checked Exception的方法,如readLine方法。
b、程序运行中发生错误,并且用throw(不是throws)关键字抛出一个checked Exception。
注意:a、不需要声明Java的内部错误,也就是那些从Error类继承来的错误。
b、不应该声明从RuntimeException继承来的异常。
总之,一个方法必须声明它可能抛出的全部checked Exception,而unchecked Exception要么是不可控制的(Error),要么是开发人员应该避免的(RuntimeException)。如果方法没有声明所有checked Exception,则编译器会给出错误信息。
2)抛出异常
假设有一个ShenXian类,类中有一个shiFa方法。正常情况下,每个神仙都可以变法术,但法术也有偶尔失灵的情况(异常)。此时,可以有shiFa这个方法抛出一个Exception异常对象。
class ShenXian{
//连续五次施法则法术失灵
public void shiFa(int i) throws Exception{//使用throw关键字在shiFa这个方法头上声明异常
if(i == 5){
throw new Exception();//使用throw关键字抛出自定义异常对象。
}
System.out.println("第"+i+"次施法成功");
}
public static void main(String [] args) throws Exception{//使用throws关键字声明异常并将异常传给JVM
ShenXian shenxian = new ShenXian();//建立一个ShenXian类的对象
for (int i = 1; i < 7;i++){
shenxian.shiFa(i);
}
}
}
运行结果:
第1次施法成功
第2次施法成功
第3次施法成功
第4次施法成功
Exception in thread "main" java.lang.Exception
at ShenXian.shiFa(ShenXian.java:5)
at ShenXian.main(ShenXian.java:12)
shiFa这个方法头上用throws声明了可能发生的异常,并将该异常交给它的调用者去处理,它的调用者是主函数,也就是说让主函数去处理。但是主函数也用throws关键字声明了异常,并将该异常交给它的调用者去处理,它的调用者是JVM。也就是说,如果shiFa这个方法运行时发生了异常,它会用throw关键字抛出一个异常对象,这个对象最后会被传给虚拟机,交由虚拟机去处理。从打印出来的代码来看,前四次施法都成功了,打印出了代表施法成功的语句,当准备进行第5次时施法时,发现i == 5
这个条件满足,然后开始执行throw new Exception();
这条语句,抛出了一个异常对象,交给了主函数,主函数又将该异常对象交给了JVM,JVM调用默认处理异常的方法,在屏幕上打印出相关的异常信息。
5、处理异常
如果一个Java方法碰到异常,有两种处理方式:一是在方法内捕获(try-catch)这个异常,二是将异常声明(throws),由调用该方法的上级方法处理。对于第一种处理方式,一般采用try-catch-finally三段论的方式,代码格式如下:
try{
需要被检测的代码;
}
catch(异常类 变量){
处理异常的代码(处理方式)
}
finally{
无论是否发生异常都会执行的代码
释放资源,如关闭I/O流,断开数据库连接。
}
结合下面代码进行分析:
class Demo{
int div(int a,int b) throws ArithmeticException{
return a/b;
}
}
class Test{
public static void main(String [] args){
Demo d = new Demo();
System.out.println("除法运算开始");
try{
int x = d.div(4,0);
System.out.println("运算结果x="+x);
}
catch(Exception e){
System.out.println("Exception:除数不能为零");
}
finally{
System.out.println("清理除法运算后的垃圾");
}
}
}
运行结果:
除法运算开始
Exception:除数不能为零
清理除法运算后的垃圾
Demo类中的div方法头中用throws声明了ArithmeticException异常,也就是说如果div方法运行时出现了异常,会将该异常交给它的调用者(主函数)去处理,主函数处理的方式就是使用try-catch-finally三段论方式。结合打印结果来分析,虚拟机执行完System.out.println("除法运算开始");
这条语句后,开始执行try代码块中的被检测语句,首先执行的是int x = d.div(4,0);
这条语句,因为要调用div方法,且div方法有throws声明了异常,所以div方法会抛出一个ArithmeticException异常对象给try代码块,try代码块会结合该异常对象来分析被检测代码是否属于这类异常,由于Java中的ArithmeticException异常类定义了除数不能为零,所以检测结果是int x = d.div(4,0);
这条语句确实属于ArithmeticException异常,这时候JVM就不在继续执行异常代码下面的代码了,也就是不执行System.out.println("运算结果x="+x);
这条语句了,并且会将由int x = d.div(4,0);
引起的异常交给相对应参数的catch代码块,然后JVM就会执行catch代码块中的处理该异常的方法。
处理语句的其他格式:
a、
try{
}
catch{
}
b、
try{
}
finally{
}
6、自定义异常类
虽然Java语言提供了许多异常种类,但是有些时候这些异常仍然不够用。例如,假设要设计一个给小学低年级学生使用的计算器,该计算器在执行减法运算时要保证减数不能大于被减数,如果发生了这种错误,则要抛出一个异常。但是Java异常体系中没有定义这样一个异常,此时就有必要建立一个自己的异常类。
建立一个自己的异常类很简单,步骤是新建一个类,并使该类继承Exception(一般都会选择直接继承Exception,或者继承RuntimeException以及它的子类这两种情况)。先来看下IOException源码是如何继承Exception的?
public class IOException extends Exception{
static final long serialVersionUID = 7818375828146090155L;
public IOException(){
super();
}
public IOException(String message){
super(message);
}
public IOException(String message,Throwable cause){
super(message,cause);
}
public IOException(Throwable cause){
super(cause);
}
}
类IOException直接继承了Exception,然后覆盖了Exception中的各个构造方法。我们可以参照它来建立我们自己的异常类。
public class BasicCalculator{
//减法运算
//使用throws关键字声明SubstractException异常,并将异常传给调用者处理。
public int substract(int i,int j) throws SubstractException{
if(i < j){
SubstractException e = new SubstractException();
throw e;//使用throw关键字抛出一个SubstractException异常对象。
}
else{
return i-j;
}
}
//加法运算
//使用throws关键字声明ArithmeticException异常,并将异常传给调用者处理。
public int add(int i,int j) throws NegativeException{
if(i < 0 || j < 0){
throw new ArithmeticException();
}
return i+j;
}
//减法异常
class SubstractException extends ArithmeticException{
public SubstractException(){
super("减数大于被减数");
}
}
//负数异常
class NegativeException extends ArithmeticException{
public NegativeException(){
super("出现负数的异常");
}
}
public static void main(String [] args){
BasicCalculator cal = new BasicCalculator();
cal.add(2,5);
cal.add(-2,-3);
cal.substract(2,3);
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException
at BasicCalculator.add(BasicCalculator.java:17)
at BasicCalculator.main(BasicCalculator.java:37)
从程序运行的结果看,其报错机制与Java语言自带的异常类没有什么不同。
7、异常的其他事项
1)异常的好处:
a、将问题进行封装。
b、将正常流程代码和问题处理代码相分离,方便于阅读
2)异常的处理原则:
a、处理方式有两种:try或者 throws。
b、调用到抛出异常的功能时,抛出几个,就处理几个。一个try对应多个catch。
c、多个catch时,父类的catch放到最下面。否则编译会报错,因为其余的catch语句执行不到。
d、catch内,需要定义针对性的处理方式。不要简单的定义printStackTrace,输出语句。也不要不写。当捕获到的异常,本功能处理不了时,可以继续在catch中抛出。例如,
try
{
throw new AException();
}
catch (AException e)
{
throw e;
}
如果该异常处理不了,但并不属于该功能出现的异常。可以将异常转换后,在抛出和该功能相关的异常。或者异常可以处理,当需要将异常产生后和本功能相关的问题提供出去,让调用者知道并处理。也可以将捕获异常处理后,转换新的异常。这样就好比在给别人转账时,如果ATM机出现故障,这时可以另外找地方去转,也可以告诉对方,转账不成功。
try
{
throw new AException();
}
catch (AException e)
{
对AException处理;
throw new BException();
}
2)异常的注意事项:
A、异常在子父类覆盖中的体现:
a,子类抛出的异常必须是父类的异常的子类或者子集。
b,如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。
/*
Exception
|--AException
|--BException
|--CException
*/
class AException extends Exception{}
class BException extends AException{}
class CException extends Exception{}
class Fu{
void show()throws AException{
}
}
class Test{
void function(Fu f){
try{
f.show();
}
catch (AException e){
}
}
}
class Zi extends Fu{
void show()throws CException{
//如果这里子类抛出CException,父类中的catch就无法处理,
//这样就会导致编译失败,所以子类只能抛出父类中的异常或子集
}
}
B、问题在内部被解决就不需要声明。
C、catch是用于处理异常。如果没有catch就代表异常没有被处理,如果该异常是checked Exception,那么必须声明。