---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
异常是程序运行过程中出现的错误。java语言的异常处理机制是java语言健壮性的一个重要体现。
一、异常体系
图1 java异常体系
下面将详细讲述各异常之间的区别与联系:
1、Throwable:
Throwable类是所有异常和错误的超类,它有两个子类Error和Exception,分别表示错误和异常。只有此类(或其子类)的对象才能通过java虚拟机或java throw语句抛出。自定义异常时,请勿直接继承本类。
2、Error
Error是Throwable的子类,表示仅靠程序本身无法恢复的严重错误,用于指示合理的应用程序不应该试图捕获的严重问题。大多数Error与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题。例如,Virtual MachineError(java虚拟机运行错误)。
在执行该方法期间,无需在方法中通过throws声明可能抛出但未捕获的Error的任何子类,因为java编译器不去检查它。因此,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,或者用throws语句声明抛出它,程序仍会编译通过。
3、Exception
Exception是Throwable的子类,表示程序本身可以处理的异常。它又分为检查异常(Checked Exception)和不检查异常(Unchecked Exception)两类,而不检查异常又称为运行时异常(RuntimeException)。
1)不检查异常(Unchecked Exception):指RuntimeException类及其子类异常。RuntimeException是那些可能在java虚拟机正常运行期间抛出的异常的超类。java编译器不去检查它。因此,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,或者用throws语句声明抛出它,程序仍会编译通过。这种异常可以通过改进代码来避免。例如,NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等。
2)检查异常(Checked Exception):指除了RuntimeException类及其子类外,其他的Exception类及其子类。java编译器会检查它。因此,当程序中可能出现这类异常时,要么用try...catch语句捕获它,要么用throws语句声明抛出它,否则程序会编译不通过。例如,IOException(输入输出异常)。
异常体系的特点:异常体系中的所有类及建立的对象都具备可抛性,也就是说可以被throw和throws关键字所操作。只有异常体系具备这个特点。
二、java异常处理机制
java异常处理涉及到五个关键字,分别是:try、catch、finally、throw与throws。通过认识这五个关键字,即可掌握基本的异常处理知识。
1、异常处理的基本语法
try
{
需要被检测的代码;
}
catch()
{
异常处理代码;
}
finally
{
方法返回之前,一定会执行的代码;
}
以上语句分为三个代码块:
1)try语句块:try语句块中代码受异常监控,其中代码发生异常时,会抛出异常对象。
2)catch语句块:catch语句块会捕获try语句块中产生的异常并在其代码块中做异常处理。
3)finally语句块:finally语句块紧跟着catch语句块,其中的代码总是会在方法返回之前执行,而不管try语句块中是否发生异常。目的是给程序一个补救的机会。
注意事项:
1)try、catch、finally三个语句块均不能单独使用,三者可以组成try...catch...finally、try...catch、try...finally三种结构。catch语句块可以有一个或多个,finally语句块最多一个。
2)try、catch、finally三个语句块中变量的作用域为语句块内部,分别独立而不能相互访问。如果要在三个语句块中均可以访问,则需要将变量定义到这些快的外面。
3)多个catch块时,匹配catch语句的顺序是由上至下。当实际抛出的异常对象是某个catch块的异常类型或其子类的实例,则执行该catch语句块代码,而不会再执行其他catch语句块。
4)可嵌套try...catch...finally结构。
5)在以下几种特殊情况下,finally语句块不会被执行:在前面的代码中使用了System.exit()退出程序;在finally语句块中发生了异常;程序所在的线程死亡;关闭CPU。
例1:异常处理
class Test
{
public static void main(String[] args)
{
try
{
int x=div(4,0); //除数为零,抛出异常
System.out.println("x="+x); //此句代码未被执行
}
catch (Exception e) //Exception e=new ArithmeticException();
{
System.out.println("除数为零!");
System.out.println(e.getMessage());
System.out.println(e.toString()); //显示异常名称,异常信息
e.printStackTrace(); //打印异常的堆栈的跟踪信息,显示异常名称,异常信息,异常出现的位置,是JVM默认的异常处理机制。
}
}
public static int div(int a,int b)
{
return a/b;
}
}
输出结果:
除数为零!
/ by zero
java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
at Test.div(Test.java:39)
at Test.main(Test.java:26)
2、throws和throw语句
1)throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。语法格式:throws 异常类型1,异常类2,...,异常类型n。
2)throw关键字用于方法体内部,用来抛出一个Throwable类型的异常。语法格式:throw 异常对象 。
注意事项:
1)如果抛出的是不检查异常,即RuntimeException和Error或它们的子类,那么可以不必用try...catch语句捕获或用throws语句声明要抛出的异常。编译仍能通过,但在运行时会被系统抛出。
2)如果抛出的是检查异常,那么要么用try...catch语句捕获它,要么用throws语句声明抛出它,否则程序会编译不通过。
3)throw语句后不允许有其他语句紧跟,因为没有机会执行这些语句。
4)异常在子父类覆盖中的体现:
①子类在覆盖时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或该异常的子类。
②如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
③如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常;如果子类方法发生了异常,则必须要进行try处理,绝对不能抛。
三、自定义异常
在实际项目中会出现特有的问题,而这些问题并未被java所描述并封装成对象。所以对于这些特有的问题,可以按照java对问题封装的思想,将特有的问题,进行自定义的异常封装。
自定义类必须继承Exception类或其子类。因为根据异常体系的特点可知,只有这个体系中的类和对象才可以被throws和throw语句操作。
例2:自定义异常
class FuShuException extends Exception //自定义异常需要继承Exception类或它的子类
{
private int value;
FuShuException()
{
super();
}
FuShuException(String msg,int value)
{
super(msg); //通过调用父类构造方法,来自定义异常信息
this.value=value;
}
public int getValue()
{
return value;
}
}
class Test
{
public static void main(String[] args)
{
try
{
int x=div(4,-1);
System.out.println("x="+x);
}
catch (FuShuException e)
{
e.printStackTrace();
System.out.println("除数为"+e.getValue());
}
}
public static int div(int a,int b)throws FuShuException //方法中抛出了FuShuException异常对象,且未进行捕获,因此需要用throws语句声明
{
if(b<0)
throw new FuShuException("除数是负数",b); //用throw语句抛出异常对象
return a/b;
}
}
输出结果:
FuShuException:除数是负数
at Test.div(Test.java:38)
at Test.main(Test.java:26)
除数为-1
四、异常转型和异常链
异常转型:即捕获到异常后,将异常以新的类型的异常再抛出。这样做一般为了让异常的信息更加直观。
例3:异常转型
class Test
{
public static void main(String[] args)
{
Teacher t=new Teacher("王老师");
try
{
t.prelect();
}
catch (NoplanException e)
{
e.printStackTrace();
System.out.println("放假!");
}
}
}
//电脑教学案例
class MaoyanException extends Exception
{
MaoyanException(String msg)
{
super(msg);
}
}
class NoplanException extends Exception
{
NoplanException(String msg)
{
super(msg);
}
}
class Computer
{
private int state=2;
public void run() throws MaoyanException
{
if(state==2)
throw new MaoyanException("电脑冒烟了");
System.out.println("电脑正常运行");
}
public void reset() //重启电脑
{
state=1;
System.out.println("电脑重启");
}
}
class Teacher
{
private String name;
private Computer c;
Teacher(String name)
{
this.name=name;
c=new Computer();
}
public void prelect() throws NoplanException
{
try
{
c.run();
}
catch (MaoyanException e)
{
e.printStackTrace();
throw new NoplanException("课时无法继续"); //异常转换,让调用者更易理解
}
}
}
输出结果:
异常链:
jdk1.4以后版本中,Throwable类支持异常链机制。顾名思义,就是将异常发生的原因一个串一个的串起来,即把底层的异常信息传给上层,这样逐层抛出。
例3中,我们看到了NoplanException异常的信息,但不知道引起它的异常的信息,而这点可以通过异常链解决。
例4:异常链
class Teacher
{
private String name;
private Computer c;
Teacher(String name)
{
this.name=name;
c=new Computer();
}
public void prelect() throws NoplanException
{
try
{
c.run();
}
catch (MaoyanException e)
{
e.printStackTrace();
NoplanException np= new NoplanException("课时无法继续");
np.initCause(e); //异常链
throw np;
}
}
}
输出结果:
五、java异常处理的原则和技巧
1、避免过大的try语句块,不要把不会出现异常的代码放到try语句块中,尽量保持一个try语句块对应一个或多个异常。
2、细化异常的类型,不要不管什么类型的异常都写成Exception。
3、catch语句块尽量保持一个语句块捕获一类异常,不要忽略捕获的异常,捕获后要么处理,要么重新抛出新类型的异常等。
4、不要把自己能处理的异常抛给别人。
5、不要用try...catch语句参与控制程序流程,异常处理机制的根本目的在于处理程序的非正常情况。
延伸阅读:
1、如何设计异常处理的代码?
2、什么时候需要抛出异常?
3、捕获到了异常时该怎样处理?
要了解如何进行异常处理设计,请参阅这篇文章“java异常处理终结篇——如何进行java异常处理设计”。