浅谈Java异常处理机制
(-----文中重点信息将用红色字体凸显-----)
一、Java异常简述:
程序异常从某种意义上说就是程序发生了错误,使程序无法再继续执行下去;但它和错误还是有区别的。例如,我们将关键字public写成了pbulic,那么虚拟机是无法编译通过的,这就属于严格意义上的错误,需要程序员更正过后程序才能编译成功。
程序员在写代码的过程中应该避免书写上的错误,但在实际的软件开发中,一个项目是由多个人员共同完成,他们会互相引用对方完成的程序,有的“错误”是由于调用者不当操作而引发的,这是原程序员无法预料的,这种情况就需要原程序员对可能发生的问题进行预先处理。例如如下代码:
<span style="font-size:14px;">class Demo1
{
public static void main(String[] args)
{
Demo d = new Demo(5,0);
d.getAnswer();
}
}
class Demo
{
int a,b;
Demo(int a,int b)
{
this.a=a;
this.b=b;
}
void getAnswer()
{
System.out.println("a/b="+(a/b));
}
}</span>
以上代码,假设Demo类是由原程序员编写的代码,而调用者在类Demo1的主函数中建立了类Demo的对象并调用了类Demo中的getAnswer()方法,则当调用者将b定义为0时,将发生java.lang.Arithmetic.Exception:/ by zero异常。
从常识上来说,我们也该知道数学计算中除数不能为0,调用者在不知情的情况下将b设定为0就引发了错误。其实原程序员在书写此段代码的时候就应该考虑到调用者可能将除数设置为0的情况,他就应该对这种情况进行处理。
但是该怎么处理呢?这就引出了本文的主题-----Java异常机制。
二、Java异常机制
Java把异常当作对象来处理,并定义了Throwable类作为所有异常和错误类的超类(父类),Throwable类下有两个小弟,分别是Error和Exception,其主要构架如下:
<span style="font-size:14px;">Throwable
|--Error
|--Exception</span>
如果程序发生Error,是无法通过编译的,需要程序员手动对代码进行修整后在编译;一般不编写针对性的代码对其进行处理。
而对于Exception异常类,它又可以分为编译时异常(CheckedException)和运行时异常(UncheckedException,又叫做RuntimeException),RuntimeException有其特殊之处,我们将在下面进行详细介绍。对于Exception异常,我们可以预先书写相关代码对可能发生问题的代码进行处理,而如何处理就是本文要讲述的重点。
三、Exception异常详述:
1.异常处理格式:
<span style="font-size:14px;"> try
{
可能触发异常的代码;
}
catch(异常类 变量)
{
具体处理方式;
}
finally
{
一定会运行的代码,一般用于释放内存资源;
}</span>
我们将可能触发异常的代码放在try{}语句块中,当它发生异常后会产生一个异常对象并由catch{}块捕获并处理,处理的方式有两种,一种是抛出异常(用throw语句),一种是直接对发生的异常进行处理,比如可以输出异常信息(在实际项目编程中我们不建议通过打印异常信息作为处理方式,因为对于专业的程序员来说这些异常信息他都比较熟悉了,没有必要而且也无法解决实际存在的问题,我们一般要针对异常做出合理的动作进行处理)。
将本文第一个程序套用异常处理格式后:
<span style="font-size:14px;">class Demo1
{
public static void main(String[] args)
{
try
{
Demo d = new Demo(5,0);
d.getAnswer();
}
catch (ArithmeticException e)
{
System.out.println("除零啦!");
}
}
}
class Demo
{
int a,b;
Demo(int a,int b)
{
this.a=a;
this.b=b;
}
void getAnswer()
{
System.out.println("a/b="+(a/b));
}
}</span>
如果异常没有发生,则不会产生异常对象,那么catch(){}语句块则不会接收异常对象,从而里面的代码不会执行。
对于打印异常对象信息,有如下方法可以实现:
<span style="font-size:14px;">System.out.println(e.getMessage());
System.out.println(e.toString());
e.printStackTrace();</span>
以上三种方法,打印异常对象信息丰富程度依次递增。
2.throws和throw:
做为原程序员,如若在编写代码时意识到调用者可能触发异常,那么可以对调用者调用时可能发生异常的代码进行"异常声明",也就是通过"throws"抛出异常对象来提醒调用者必须对次此抛出动作进行处理或者继续抛出异常(懒鬼^-^),如果调用者不进行处理(就是套try...catch格式)或继续抛出动作,那么程序无法编译通过。
继续抛出格式:
<span style="font-size:14px;">class Demo1
{
public static void main(String[] args) throws ArithmeticException
{
Demo d = new Demo(5,0);
d.getAnswer();
}
}
class Demo
{
int a,b;
Demo(int a,int b)
{
this.a=a;
this.b=b;
}
void getAnswer() throws ArithmeticException
{
System.out.println("a/b="+(a/b));
}
}</span>
从以上代码中可以看出,由于Demo类中抛出了异常,主函数调用可能存在问题的方法时也抛出了相同的异常,这样这个程序便可以通过编译,但是遗憾的是还是发生了错误,因为除数为0啦!
也许很多同仁会有个疑问,如果调用者也是抛出异常而不进行处理,那么异常这个"烫手的山芋"抛到什么时候是个头啊?其实如果异常不停的声明抛出则最终异常会抛给虚拟机(VM),虚拟机最终输出异常信息作为结束,程序也不能理想的执行。
需要在这里提一句:在抛出异常时,一定要有针对性的抛出,空指针异常、数组指标越界异常、算术异常...是什么异常就抛出什么异常,不建议总是盲目的抛出"throws Exception",这个过于笼统。
有的时候需要抛出异常的代码中可能包含了多种可能发生的异常,这个时候我们就根据具体情况抛出多种异常,每种异常之间用","隔开;调用者调用可能发生异常的代码时也应该对应的抛出多个异常。如果调用者选择处理,则catch块则应该同抛出异常的种类和个数匹配(当然你也可以只定义一个catch块,catch块中定义"Exception e",如此则此catch块可以接受任何形式的异常对象,但是这样没有针对性从而降低程序的健壮性);可以了解到,不可能有两个或两个以上的catch块一起执行,函数中有异常发生时,此行代码之后的代码都不会被执行了。
在抛出多个异常时,对应使用catch块进行处理,如果这些catch中的异常类存在子父关系,则父类异常应该放在下面;如果包含父类异常对象的catch块放在包含子类异常对象的catch块之上,则后者无法获取执行机会。
try
{
Demo d = new Demo(5,0);
d.getAnswer();
}
catch (ArithmeticException e)
{
System.out.println(e.toString());
}
catch (ArrayIndexOutOfBoundsException e)
{
System.out.println(e.toString());
}
catch (Exception e)
{
System.out.println(e.toString());
}
在讲述throw之前插播一小节-----自定义异常,否则单独讲述throw不易理解。
3.自定义异常:
虽然Java已经为我们定义了种类丰富品种齐全的异常类,但是还是会有一些异常无法同现成异常完全匹配,这时我们就可以自己定义异常类,来描述和解决特殊问题。
我们都知道Java中的老大是Object,而异常的老大就是Throwable,在Throwable类中已经定义了异常信息及获取异常信息的方法:
class Throwable
{
private String message;
Throwable(String message)
{
this.message=message;
}
public String getMessage()
{
return message;
}
}
而Throwable类的子类Exception类也定义了类似信息和获取信息的方法,不过二者具有继承关系,则可以如此定义Exception类:
class Exception extends Throwable
{
Exception(String message)
{
super(message);
}
}
我们在定义自定义异常时,需要继承我们在定义自定义异常时需要继承Exception类,由于我们自己建立的自定义异常类虚拟机是无法识别的,所以我们需要抛出(使用throw抛出异常类对象,例如:throw new MyException();)自定义异常类对象让虚拟机获取,这时包含抛出自定义异常类对象的方法也需要抛出(throws MyExcepiton)异常信息,二者都指向自定义异常类。
下面本人自定义一个异常并定义获取异常信息的方法:
class ZiDYException extends Exception
{
private int value;
ZiDYException(String msg,int value)
{
super(msg);
this.value = value;
}
public int getValue()
{
return value;
}
}
class GJClass
{
int div(int a,int b) throws ZiDYException
{
if (b<=0)
{
throw new ZiDYException("被除数不能≤0!",b);//抛出一个自定义异常对象;
}
return a/b;
}
}
class ZiDYExceptionTest
{
public static void main(String[] args)
{
GJClass g = new GJClass();
try
{
int x = g.div(4,-1);
System.out.println("x="+x);
}
catch (ZiDYException e)
{
System.out.println(e.toString());
System.out.println("被除数不符合要求,请重新输入!");
System.out.println("坏因子是:"+e.getValue());
}
System.out.println("over!");
}
}
以上代码中我在定义除法的时候抛出了异常对象,同时此方法也抛出了自定义异常类;主函数在调用抛出了异常的方法div时需要进行处理,要么继续抛出要么就使用try...catch块来进行处理,可以看到catch块中定义的异常对象为自定义异常类对象,否则无法调用我异常类中的getValue()方法。
详细讲解下抛出自定义异常类对象:
int div(int a,int b) throws ZiDYException
{
if (b<=0)
{
throw new ZiDYException("被除数不能≤0!",b);//抛出一个自定义异常对象;
}
return a/b;
}
上面代码中,我在抛出自定义异常类对象时加入了异常信息和异常因子,这些都是要在定义自定义异常类时的构造函数相对应的。当程序发生异常时,就会输出自定义的异常信息。
这里插入一句:只有属于Throwable类及其子类才具有可抛性。
现在再来补充讲解一下throws和throw的区别:throws使用在函数上,throw使用在函数内;throws后面跟异常类,可以跟多个,每个异常类之间用","隔开,throw后跟的是异常对象。
4.屌炸天的RuntimeException:
前面也提到了这个RuntimeException类,对于此类及其子类,如果在函数内容抛出了该异常类或子类对象,则函数上可以不用声明,编译照样可以通过;如果在函数上声明了该异常,调用者可以不用进行处理,编译依然可以通过。这样做的原因在于出现此类问题时,虚拟机希望程序停下来让程序员修正程序,而不希望这个问题被处理所"隐藏"。
一般来说,这类问题不是定义方法的程序员的错,而是调用者没有输入合理的数值导致异常的发生,所以有必要也有可能对出现异常的代码就行更正。我们在定义自定义异常时,如果异常发生后导致程序无法继续执行,则可以将自定义异常类继承RuntimeException类。
5.finally和try...catch语句的嵌套:
finally语句也是异常处理代码块的一部分,在此代码块中书写的程序无论异常是否处理都会运行,一般我们利用finally块来对内存进行释放。
在有的特殊情况,try...catch语句可以进行嵌套,由此对异常进行多次判断,这个有点像for语句的嵌套,不过不是一回事。
//当捕获到的异常本功能处理不了时可以继续在catch块中抛出
public void method()
{
try
{
throw new Exception();
}
catch(Exception e)
{
try
{
throw e;
}
catch
{
System.out.println(“异常嵌套");
}
}
}
四、零散知识点总结:
1.一旦在finally块中使用了return或throw语句,将会导致try块、catch块中的return、throw语句失效。
2.如果两个类具有父子关系,则:① 若子类覆盖父类方法,父类方法抛出异常,则子类只能抛出与父类相同的异常或父类异常的子异常; ② 若子类覆盖父类方法,子类方法包含多个异常,则子类只能抛出与父类相同的异常或父类异常的子异常,剩余的其他异常则必须通过try..catch来处理;③ 若子类覆盖父类方法,如果父类方法没有抛出异常,则子类方法也不能抛出异常,如果子类方法确实有异常存在则必须通过try...catch来处理。
3.try/catch/finally三个块可以三个块同时出现,也可以try块和catch块或者finally搭配,不允许单个块独自出现。
4.finally块有一种情况不会执行,就是执行到"System.exit(0);"语句时。
本文就写到这儿啦!有的知识点没有详述,如果朋友们有兴趣可以给我留言,我会及时回复的;文中难免有问题,还请各位读者海涵啦^..^!