这两天看完了传智播客毕向东老师《javaSE基础视频》中有关异常视频的章节,收益颇多,于是整理出来,欢迎大家留言讨论。(既不是转载,也不是翻译,笔者冒昧选了原创)
一:异常的来源?什么是异常?异常的分类
我们在c语言中写一个方法时,会用到诸如if...else这样的语句,如果情况较为复杂,if...else语句的下面会有许多语句,这样使得代码的阅读性并不是很好。在java中对这种情况进行了改良,我们把像if...else语句下的这些语句(可以理解为异常情况)用类的形式进行了描述和封装。于是就有了java中的异常体制。
异常:在程序运行时期发生的不正常情况
异常类:描述不正常情况的类
简而言之:异常就是java通过面向对象的思想将问题封装成了对象,用异常类对其进行描述。
问题很多,意味着描述的类也很多,将其共性不断进行向上抽取,最终不正常的情况就分成了两大类
1>一般不可处理的 Error类
Error类特点:是由jvm抛出的严重性问题,影响到程序的执行,这种问题发生一般不可针对性处理,需要直接修改程序。
2>可以处理的:Exception(异常)类<是我们的主要学习内容>
这两个类都有一个共同的父类:Throwable(可抛类),凡是Throwable下的子类都具备可抛性,这也是该体系的特点。我们可以理解为:无论是Error类还是Exception类,都是问题,问题发生了就可以抛出,让调用者知道并处理。
注:可抛性的体现:凡是可以被throw和throws这两个关键字所操作的类和对象都具备可抛性。
二: 自定义异常 <这部分一般会放在最后讲,但是我觉得老师放在前面讲更容易接受>
如果要让一个类成为异常类,那么这个类必须继承异常体系,因为只有成为异常体系,才有资格具备可抛性,被throw和throws两个关键字所操作,异常处理方式一般分为声明(throws ,throw)和捕捉(try...catch)
下面举一个例子:一般在数组中容易出现数组越界这种异常,现在为了描述的更细致一点,我们自定义一个异常类来描述这种情况和数组角标越界以及空指针这三种异常
/* 这是一个普通类,在这个类中定义了一个method方法,主函数执行到method方法会在这个类中执行。
在这个类中,写了三种异常,由于数组越界的异常和空指针的异常时异常体系中本来就用的,所以这两个异
常类我们并不需要自己建立,但是我们也对其进行了改变,在API可以查到中这两个异常类的传参的时候是可
以把参数传成字符串格式的,所以在传参的时候我们传入了一些字符串,相当于他们会在出异常时通过控制
台输出来
*/
public class Demo {
public int method(int[] arr,int index)//如果自定义的子类继承的是Exception这里要跟throws FuShuIndexException
{
if(arr==null)//java编译器会先检查基本的语法错误在检查逻辑错误
throw new NullPointerException("数组的索引不能为空!");
if(index>=arr.length)
throw new ArrayIndexOutOfBoundsException("数组的角标越界,哥们你是不是疯了"+index);
if(index<0)
throw new FuShuIndexException("角标变成负数啦");
return arr[index];
}
}
/*这个类是我们定义的类,在这个类中定义了两个构造函数,一个空参构造函数,还有一个接收String
类型的构造参数,根据我们在继承那块儿学的知识,我们要在所要执行的构造函数的第一句写上super语
句,让它去执行父类
*/
public class FuShuIndexException extends /*Exception*/ RuntimeException{
//如果把Exception变成runtimeException就不用再所属异常中抛异常了
FuShuIndexException()
{}//空的构造函数,笔者认为这个写不写都无所谓,经验证,的确可以不写
FuShuIndexException(String msg)
{
super(msg);//带着这个信息去找父类的构造器,执行父类
}
}
//对于初学者,这块儿可能有点难理解,不妨把这个当成一个语法记下来,用的多了,会有
更深层次的理解
/* 这个是一个普通的测试类*/
public class Test{
public static void main(String[] args)//throws FuShuIndexException
{
int[] arr=new int[3];
Demo d=new Demo();
int num=d.method(arr,-30);
//int num=d.method(null,-30);
System.out.println("over");
}
}
将主函数method方法传的参数稍作改变,就可以测试三个不同的异常类了,以下为程序运行结果
注:读者可能会对程序中注释的Exception和RuntimeException有点疑惑,java中有这样一条语法规则:
异常可以分为两类
1.编译时被检测异常: 除了RuntimeException中的其他所有Exception的类和其子类
这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式,这样的问题
都可以进行针对性处理
2.编译时不检测异常:就是Exception中的RuntimeException和其子类
这种问题的发生,无法让功能继续,运算无法进行,更多是因为调用的原因导致的或者引发
了内部状态导致的,那么这种问题一般不处理,直接编译通过,让调用者调用的程序强制停止,
让调用者对代码进性修正
可以理解为:RuntimeException更厉害,如果创建的异常继承的是这个类,在方法和main方法的后面就不用加throw 了,而我们更多见到的是继承Exception,这时就必须加上throws 以及自定义的异常类名了。
三:关于throw和throws的用法区别
在上面的例子中我们看到抛异常使用了这两个关键字,throw和throws是用于对异常来进行声明的
1 二者用法的区别:
throws使用在方法上,throw使用在方法内
2.throws抛出的是异常类,throw抛出的是异常对象
3.throws可以抛出多个,中间用逗号隔开,throw抛出的是一场对象,只能抛一个
四:异常处理的捕捉形式
这是可以对异常进行针对性处理的方式
具体格式是:
try
{ //需要被检测的异常代码
}
catch(异常类 变量)//这个变量是专门用于接收当前所发生的异常对象
{
//处理异常的代码
}
finally
{ //最终化的代码,一定会被执行的代码
}
下面举一个例子,这个例子简化了上面的例子,但是用到了try...catch语句
public class Demo2 {
public int method(int[] arr,int index)throws FuShuIndexException2
{
if(index<0)
throw new FuShuIndexException2("角标变成负数啦");
return arr[index];
}
}
public class FuShuIndexException2 extends /*Exception*/ RuntimeException{
//如果把Exception变成runtimeException就不用再所属异常中抛异常了
FuShuIndexException2(){}//空的构造函数
FuShuIndexException2(String msg)
{
super(msg);//带着这个信息去找父类的构造器,执行父类
}
}
前面两个类与之前差不多
public class ExceptionDemo2 {
public static void main(String[] args)
{
int[] arr=new int[3];
Demo2 d=new Demo2();
try {
int num=d.method(arr,-20);
System.out.println(num);
//System.out.println(arr[30]);
//System.out.println(arr[-8]);
}
catch(FuShuIndexException2 e)
{
System.out.println("Message"+e.getMessage());
System.out.println("String"+e);
//此时不像之前学习的那样,打印类型+哈希地址
//因为异常对象建立了自己独特的表现形式,所以此时打印出来的是字符串
//他自动的在调用自己的tostring方法
e.printStackTrace();
//jvm的异常处理机制就是调用异常对象的这个方法
System.out.println("负数角标异常!!!");
}
//int num=d.method(null,-30);
System.out.println("over");
}
}
其中备注的较清楚,以下是执行结果
这里有一个面试时的常见问题:
一个try后面课以跟多个catch,但是通常要把父类的catch放在最下面,为什么?
答:父类的catch一般是共性错误,也可以认为是自己生命的那几个错误之外的其他错误,如果
这个共性错误放在最前面,相当于自己声明的catch是无用的,没有意义,这时编译器会报错。
下面这个例子是try,catch,finally相结合使用的
public class Demo {
public int show(int index)throws ArrayIndexOutOfBoundsException
{
if(index<0)
throw new ArrayIndexOutOfBoundsException("越界啦");
int[] arr=new int[3];
return arr[index];
}
}
public class ExceptionDemo5 {
public static void main(String[] args) {
Demo d=new Demo();
try {
int num= d.show(-3);
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println(e.toString());
// return ;//加上return语句finally还执行但是over没了
System.exit(0);//退出jvm,只有这种情况finally不执行,其他都执行
}
finally {//通常用于关闭(释放)资源,凡是有资源要关闭,都往数据库中放
System.out.println("finally");
}
System.out.println("over");
}
}
结果如下
异常中的处理原则以及细节
1. 函数内部如果抛出需要检测的异常,那么函数上必须要声明,否则必须在函数内用try...catch捕捉,否则编译失败
2.什么时候catch,什么时候throws?
功能内容可以解决用catch,解决不了用throws告诉调用者,让调用者解决
3一个功能如果抛出了多个异常,那么调用时,必须有多个catch进行针对性的处理。内部有几个需要检测的异常,就抛几个异常,抛出几个,就catch几个
4.try...catch...finally组合,是常见的组合体,当没有必须对资源进行释放时,可以不用定义finally,但是一般都要进行finally进行资源的关闭
5.子类在非要改父类方法时,父类的方法如果跑出了异常,那么子类的方法只能抛出父类的异常或者改异常的子类
最后是一个具有结合性的例子
/*问题:毕老师用电脑上课,涉及的对象 ,电脑 老师
* 分析其中的问题:比如电脑蓝屏,冒烟
* */
//定义了一个电脑类state表示状态
public class Computer {
private int state =2;
public void run()throws LanPingException,MaoYanException
{
if(state==1)
throw new LanPingException("电脑蓝屏啦!!!");
if(state==2)
throw new MaoYanException("电脑冒烟啦!!!");
System.out.println("电脑运行");
}
public void reset()
{
state=0;
System.out.println("电脑重启");
}
}
public class LanPingException extends Exception{
LanPingException (String msg)
{
super(msg);
}
}
public class MaoYanException extends Exception {
MaoYanException (String msg)
{
super(msg);
}
}
public class NoPlanException extends Exception{
NoPlanException (String msg)
{
super(msg);
}
}
public class Teacher {
private String name;
private Computer com;
Teacher(String name)
{
this.name=name;
com=new Computer();
}
public void prelect()throws NoPlanException
{
try
{
com.run();
System.out.println(name+"讲课");
}
catch(LanPingException e)
{
System.out.println(e.toString());
com.reset();
prelect();
}
catch(MaoYanException e)
{
System.out.println(e.toString());
test();
throw new NoPlanException("课时无法完成 原因:"+e.getMessage());
}
}
public void test()
{
System.out.println("大家做练习");
}
}
public class Demo {
public static void main(String[] args)
{
Teacher t=new Teacher("毕老师");
try
{
t.prelect();
}
catch(NoPlanException e)
{
System.out.println(e.toString()+"......");
System.out.println("换人");
}
}
}
将state的值做改变后,结果如下
state=0时
state=1时
state=2时
以上是我整理的异常的内容,不足之处很多,欢迎大家留言讨论。