赞
踩
学习Java的人在运行自己的Java程序的时候难免会遇到下面的情况:
控制台输出了这样红色的提示符。我们称这样的一个提示为Java的一个异常。刚开始遇到这样的情况难免让人头疼,但看完这篇文章之后你就会对这个异常有一个新的认识。
异常的分类让我们打开Java的API文档看看java.lang包中的一些常见的异常:
随便打开一个异常类我们可以发现它们的父类都有Throwable,而Throwable的父类是Object:
详细的对Throwable分类后,我们可以得出以下的一张类的关系图:
上面的红字就是我们初期常见的异常了:
ArithmeticException:算术异常,常见的发生原因是在除数为0时。
ClassCastException:类转换异常,常见的发生原因是在多态发生转换时。
NumberFormatException:数字初始化异常,常见的发生原因是在字符串转换为数字时。
IndexOutOfBoundsException:数字越界异常,常见的发生原因是在遍历数组时。
NullPointException:空指针异常,常见的发生原因是在对象.方法名,对象为null时。
ClassNotFoundException:类未找到异常,常见的发生原因是在载入类时。
黑色字体可以认为只是凑数的而已。
对于Exception的直接子类,我们称之为编译时异常/受检异常/受控异常;
对于RunTimeException的直接子类,我们称之为运行时异常/非受控异常/非受检异常;
分类原因那么,为什么我们要对Throwable这么分类呢?大体上分法:
Error类发生的机率小,不用进行处理。但一旦发生我们只能修改代码结构;
RunTimeException发生的概率小,可以处理也可以不处理;
Exception的直接子类发生的概率大,必须处理,不然编译器报错。
只有适当的进行分类,我们才能对异常进行一个适度的处理。就拿运行时异常的直接子类ArithmeticException和Excepion直接子类ClassNotFoundException来看:
注:以上的forName方法就是加载类的作用,方法的字符串参数传入类名。
是不是可以看出了RunTimeException的直接子类发生的概率低,所以可以不用预处理;而Exception的直接子类因为发生的概率高,所以必须进行处理了呢。
异常的处理那么为什么我们要学会对异常进行处理呢?
我们可以看出,异常之后的Java语句就不执行了(有点return的味道)。所以我们为了保证Java程序出了异常后还能正常运行,我们需要对异常进行处理。
对于处理方式,我们还是借用上述的比喻来说明:我们生病了是不是要进行处理,这时候我们可以找父母处理,父母处理不了找医生处理,又或者我们自己处理。异常的处理也是和这样的道理差不多的,对于前者我们称之为上抛,用throws关键字进行处理;对于后者我们称之为捕捉,用try...catch进行处理。
throws
用这种处理方式的格式是在当前抛出异常的所在方法的形参列表之后的位置写上throws 对应的异常:
并且一个方法可以抛出多个异常:
但是这种方式的处理是比较不负责的。因为如果一直上抛没人解决的话,抛到了main方法,当这个异常没有发生还是正常的,但如果发生了异常开始一直上抛,最后到main方法把异常上抛给JVM后,整个程序就终止了。
对于一个方法上抛了编译时异常,那么调用它的方法需要进行处理(两种处理方式都行):
对于一个方法上抛了运行时异常,那么调用它的方法可以但不必要进行处理:
因此我们来看Class的forName方法的源代码:
因为它抛出了ClassNotFoundException大概率异常,所以我们就需要处理。
try...catch
这种处理方式是我们经常用到的,大致格式如下:
try{
...
} catch(Exception e){
...
}
大概的意思就是尝试try语句块内的代码,如果捕捉到catch后面括号声明的异常,那么就执行catch语句块内的代码。如:
当try语句块内抛出异常后,之后try语句块内的代码一样不执行,但try...catch语句块之后的语句还是会继续执行。而且对于catch语句块,有以下用法及注意事项:
catch后面的括号内的可以看作是方法参数——数据类型 标识符。这个数据类型可以使用多态。可以写具体的异常类型或父类型名称。但建议异常分开写,有助于找到异常所在处。
catch不能单独使用,需要和try联合使用;一个try可以有多个catch,但多个catch需满足子类在上,父类在下的原则。否则当catch到子类异常时也是执行父类的catch语句块,子类catch语句块就没有执行的机会了。
JDK8新特性,catch括号内支持如下的格式(多个异常可以合并):
finally
对于try...catch的结构,我们还可以在catch语句后面加finally语句块。不管是执行try语句块还是catch语句块,finally语句块一定会执行且最后执行。
尽管try或者catch语句块中有return语句时,finally语句块还是会执行(try...catch...finally...return):
但是有一个特例:当我们在try或者catch语句块中写System.exit(0);语句退出JVM时,finally语句块就没有执行了:
finally和catch一样,需要和try一起用才行;try可以只连接finally:
以上就是异常的两种处理方式了,那应该怎么选择呢?其实很简单,如果你不想在这里处理这个异常你就上抛出去。看Java的源代码就会发现,很多的异常都是直接上抛的,目的就是为了让我们自己处理这些异常。
自定义异常上面只说了Java的lang包下常见的异常及其处理方式,现在我们来学着自己创建一个异常,这个异常类的创建很简单,我们不需要学Java自带的异常类是怎么学的,我们就记住这样的公式:
public class 异常名 extends 需要继承的异常名 { // 无参构造,可有可无 public 异常名() { } // 有参构造,主要结构 public 异常名(String message) { super(message); }}
无参构造只是为了满足类的构造方法的完善度。重点看有参的构造方法,调用了父类的构造方法。一直看下去会发现调用了:
Throwable父类的构造方法,而且我们可以看出这个message主要写的就是这个异常的详细描述。比如,我写个数字不是一的异常:
public class NumberIsNotOne extends Exception { // 无参构造,可有可无 public NumberIsNotOne () { } // 有参构造,主要结构 public NumberIsNotOne (String tip) { super(tip); }}
异常写好了就又涉及到怎么新建异常并抛出了。新建简单,因为是一个类,所以我们直接new异常对象,抛出的话我们用throw关键字进行抛出。
由于我们手动抛了异常,所以一定要进行处理,这里如果自己抛自己处理那就没意思了,所以处理手动抛异常的方式都是向上抛。之后我们在main方法中调用input方法:
这里异常上抛没意义,我们用try...catch,然后用软件自动生成的try...catch语句就默认为以上的格式。
异常父类Throwable的两个方法1、软件自动生成的是异常的printStackTrace()方法。然后运行,输入一个不是1的数字。就会出现控制台的那些信息,我们会发现,构造方法传入的字符串在异常信息的冒号后面显示了。这也就是构造方法写字符串作为详细信息的作用。为什么用这个printStackTrace()方法呢?
因为这个方法可以在控制台显示出我们的异常是在哪里发生的,由上而下是一个因果关系,我们可以点击蓝色的框内的文字直接跳转到该问题发生的位置(主要看我们写的类,Java自带的肯定没有错,不需要看)。
2、当然如果我们不需要捕捉是哪一行的代码出了异常,只是想提示我们传入的字符串信息,可以用getMessage方法:
这个方法返回的是String类型。
对于异常,我们常用的就是父类Throwable的两个方法了:
tip我们不仅可以new自己的异常,也可以newJava自带的异常。比如:
不过要注意的是,这是只是创建异常对象,并没有抛出,所以不用处理异常。
tip子类覆盖的方法抛出的异常不能比父类抛出的异常多如果是一路读过来的读者不知还记得这句话不。我们在方法覆盖的时候说过这一点。现在,这个"多"应该理解为“更广泛”:
1、父类没有抛出异常,子类也不能抛
2、父类抛了异常,子类可以抛多个父类异常的同级类或子类,不能是父类
tip防止空指针异常的一个技巧这个技巧只适用在当一个对象调用equals方法去和一个字符串常量比较时:
对象.equals("字符串") 改成 "字符串".equals(对象)
因为对象有可能是null,但"字符串"肯定不是null
最后如果你还是不明白异常,那么我们从一个形象的角度来看,Throwable分类其实可以看成是模拟的人的生老病死的"病"和"死"两种状态。
对于一个程序出现了Error的错误现象,我们的开发人员是没法对之进行直接处理的。相当于我们的"死";
对于一个程序出现Exception的异常现象,我们的开发人员是可以对之进行直接处理的。相当于我们的"病"。
对于RunTimeException,相当于我们的小概率的病种,可以不用每次都预防;
对于Exception的直接子类,相当于我们的大概率病种,需要每次都预防。
对于Error也是小概率事件,不用每次都预防。
而throws上抛相当于我们病了去找别人看病,如果别人看不了又把这个抛给其他人看。
上抛到JVM时,就相当于这病抛到最后没人治,直接“没”了。
而try...catch捕获处理相当于我们病了自己治。
而throw手动上抛相当于我们说自己有哪一个病。自己这么说了一般都是希望别人来解决,所以手动上抛异常都是用上抛解决的
而手动上抛后面创建的异常可以是Java系统的也可以是我们自己写的。相当于我们说自己生病了,然后说生的病是目前医学有记载的病或者自己瞎编的一个病。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。