异常简介
- java异常是java提供的用于处理程序中错误的一种机制。
- 有异于常态,和正常情况不一样,有错误出现。从编程上来讲,把阻止当前方法或作用域,称为异常。
- 涉及良好的程序应该在异常发生时,提供处理这些错误的方法,使得程序不会因为异常的出现而阻断或产生不可预见 的结果。
- java程序的执行过程中如果出现异常事件,就会生成一个异常类对象,该异常类对象封装了异常事件的信息,并将提交给java运行时系统,这个过程称为抛出(throw)异常。
- 当java运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这一过程称为捕获(catch)异常。
- 当异常发生时,程序会中止当前的流程,根据获取异常的类型去执行相应的catch代码块。
异常的体系结构
- 1.Java中的所有不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另一个是Exception类;
- 2.其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者;
- 3.Exception类主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(需要手动添加捕获和处理语句,有:文件异常、SQL异常等)
- 4.RuntimeException异常主要包括以下四种异常:空指针异常、数组下标越界异常、类型转换异常、算术异常。
使用try..catch..finally实现异常处理
- 处理异常
try..catch以及try..catch..finally
语法:
try {
//一些会抛出异常的方法
}
catch (Excepotion1 e){
//处理该异常的代码块
}
catch (Excepotion2 e){
//处理该异常的代码块
}
…
(n个catch块)//如果没有异常产生,所有的catch代码块都会被略过不执行!
…
finally{
//资源回收块
}
注意:
- 异常处理语法结构中,只有try块是必须的。也就是说,如果没有try块,则不能有后面的catch块和finally块;
- catch块和finally块都是可选的,但catch块和finally块至少出现其中之一,也可以同时出现;
- 可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面;
- 不能只有try块,既没有catch块,也没有finally块;
- 多个catch块必须位于try块之后,finally块必须位于所有的catch块之后。
下面看一个具体的例子:
package com.xupt.test;
public class TryCatchTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TryCatchTest tct=new TryCatchTest();
int result=tct.test();
System.out.println("test()方法执行完毕!返回值为:"+result);
int result2=tct.test2();
System.out.println("test2()执行完毕!");
}
/**
* divider除数
* result结果
* try—catch捕获while循环
* 每次循环,divider减一,result=result+100/divider
* 如果:捕获异常,打印输出“抛出异常啦!”,并返回-1
* 否则:result
* @return
*/
public int test(){
int divider=10;
int result=100;
try{
while(divider>-1){
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e){
e.printStackTrace();
System.out.println("循环抛出异常了!");
return -1;
}
}
/**
* divider除数
* result结果
* try—catch捕获while循环
* 每次循环,divider减一,result=result+100/divider
* 如果:捕获异常,打印输出“抛出异常啦!”,并返回-1
* 否则:result
* finally:打印输出:“这是finally!”,同时打印输出result的值
* @return
*/
public int test2(){
int divider=10;
int result=100;
try{
while(divider>-1){
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e){
e.printStackTrace();//将该异常的跟踪栈信息输出到标准错误输出
System.out.println("循环抛出异常了!");
return result=999;
}
finally{
System.out.println("这是finally!");
System.out.println("我是result!值是:"+result);
}
}
}
输出结果:
java.lang.ArithmeticException: / by zero
at com.xupt.test.TryCatchTest.test(TryCatchTest.java:29)
at com.xupt.test.TryCatchTest.main(TryCatchTest.java:8)
循环抛出异常了!
test()方法执行完毕!返回值为:-1
java.lang.ArithmeticException: / by zero
at com.xupt.test.TryCatchTest.test2(TryCatchTest.java:54)
at com.xupt.test.TryCatchTest.main(TryCatchTest.java:10)
循环抛出异常了!
这是finally!
我是result!值是:999
test2()执行完毕!
注意:异常捕获时,一定要记住先捕获小异常,再捕获大异常!不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块的后面,否则将出现编译错误。看如下代码片段:
try {
statements…
}catch(RuntimeException e){//1
System.out.println("运行时异常");
}catch(NullPointerException ne){//2
System.out.println("空指针异常");
}
上面的代码中有两个catch块,前一个catch块捕获RuntimeException异常,后一个catch块捕获NullPointerException 异常,编译上面代码时将会在2处出现已捕获到异常java.lang.NullPointerException 的错误,因为1处的RuntimeException已经包含了NullPointerException 异常,所以2处的catch块永远也不会获得执行的机会。
java中的异常抛出以及自定义异常
java中的异常抛出
* throw:将产生的异常抛出
* throws:声明将要抛出何种类型的异常(声明)
public void 方法名(参数列表) throws 异常列表{
//调用会抛出异常的方法或者:throw new Exception();
}
具体形式:
public void divide(int one,int two) throws Exception{
if(two==0){
throw new Exception("两数相除,除数不能为0!");
}else{
System.out.println("两数相除,结果为:"+one/two);
}
}
自定义异常
- 用throws声明方法可能抛出自定义的异常,并用throw语句在适当的地方抛出自定义的异常。
public class Test{
public void regist(int num) throws MyException{
if (num<0){
throw new MyException("人数为负值,不合理",3);
}
System.out.println("登记人数"+num);
}
public void manager(){
try{regist(100);}
catch(MyException e){
System.out.println("登记失败,出错类型码="+e.getId());
e.printStackTrace();
}
System.out.print("操作结束");
}
public static void main(String[] args){
Test t = new Test();
t.manager();
}
}
class MyException extends Exception{
private int id;
public MyException(String message,int id){
super(message);
this.id=id;
}
public int getId(){
return id;
}
}
java中的异常链
对于一个真实的企业级应用而言,常常有严格的分层关系,层与层之间有着非常清晰的划分,上层功能的实现,严格依赖于下层的API,也不会跨层访问。分层结构从下到上一次为:
- 持久层:保存数据
- 中间层:实现业务逻辑
- 表现层:用户界面
当业务逻辑层访问持久层出现SQLException异常时,程序不应该把底层的SQLException异常传到用户界面,有如下两个原因:
* 对于正常用户而言,他们不想看到底层SQLException异常,SQLException异常对他们使用该系统没有任何帮助;
* 对于恶意用户而言,将SQLException异常暴露出来不安全。
把底层的原是异常直接传给用户是一种不负责任的表现。通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译。
假设程序需要实现工资计算的方法,则程序应该采用如此啊结构的代码来是实现该方法:
import javax.security.sasl.SaslException;
public class calSal throws SalExecption{
try {
//实现结算工资的业务逻辑
……
} catch (SQLException sqle) {
// TODO: handle exception
//把原始异常记录下来,留给管理员
……
//下面异常中的message就是对用户的提示
throw new SaslException("访问底层数据库出现异常");
}
catch (Exception e) {
// TODO: handle exception
//把原始异常记录下来,留给管理员
……
//下面异常的message就是对用户的提示
throw new SaslException("系统出现未知异常");
}
}
这种把原始异常信息隐藏起来,仅向上提供必要的异常提示信息的处理方式,可以保证底层异常不会扩散到表现层,可以避免向上暴露太多的实现细节,这完全符合面向对象的封装原则。
这种把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来是一种典型的链式处理,也被称为“异常链”。