处理异常
假设一个java程序在运行过程中出现了一个错误,这个错误可能是文件包含了错误信息,或者网络连接出现问题,也有可能是使用了无效的数组下标,或者是引用了一个没有被赋值的对象。如果由于出现错误导致某些操作没有完成,程序应该:
- 返回到一种安全状态,并能够让用户执行其他的一些命令
- 允许用于保存所有操作的结果,并已妥善的方式处理程序
异常处理的任务就是将控制权从从错误发生的地方转移到能够处理这种情况的错误处理器,针对不同的错误应该有不同的处理方式,所以要把错误和问题进行分类。
异常分类
在java语言中,异常对象都是派生于Throwable类(注意这个是类不是接口)的一个实例,当然用户也可以自己创建异常类,需要继承Throwable或者它的子类。
Throwable的下一层分为Error和Exception。
Error类层次结构描述了java运行时系统的内部错误和资源耗尽错误,应用程序无法处理这样的错误,只能尽力安全的终止程序。
Exception又分为RuntimeException和其他异常,如果是RuntimeException,那一定是程序错误导致的,其他异常就包括IOException,意思是程序没有问题,是由于其他比如资源文件有问题产生的I/O异常。
派生于RuntimeException的异常包含以下几种情况:
- 错误的类型转换
- 数组访问越界
- 访问null指针
其他异常包括:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 视图根据指定的字符串查找对应的Class对象,但是指定的字符串对应的类不存在
当然不只是上面所说的情况,上述只是为了方便了解两种异常的区别,简单来说记住一句话:如果出现RuntimeException,那一定是你的问题。
java语言规范将派生于Error类或者RuntimeException类的所有异常称为非受查异常,所有其他的异常称为受查异常。编译器将检查是否为所有的受查异常提供了异常处理器。所以说受查异常是一定要进行异常处理的,不然编译器会报错
声明受查异常
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
我们可以使用throws关键字来声明异常(也可以说是可能会抛出异常的意思),用throw来抛出可能发生的异常(动作)。一般来说使用throws声明的是受查异常
上面的例子是FileInputStream类中的一个构造器,它有可能抛出一个FileNotFoundException异常,如果根据name参数不能找到对应的文件,就会抛出一个FileNotFoundException异常对象(注意是对象,然后抛出是向上层抛出,表示FileInputStream这个方法不做处理,调用它的方法来处理,当然调用它的方法亦可以继续向上抛出),这时系统就会开始搜索异常处理器,以便知道如何处理这个异常对象。如果要声明多个异常,需要用逗号隔开。
注意: 如果在子类中覆盖了父类的一个方法,子类方法中声明的受查异常不能比父类方法中声明的异常更通用,如果父类方法没有声明任何异常,那么子类也不能声明异常。在使用throws声明异常的时候,尽量能具体化异常,比如上面声明FileNotFoundException而不是IOException,因为FileNotFoundException更加具体,方便看出异常原因。
如何抛出异常
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
使用throw关键字创建一个异常对象并抛出,这个抛出是一定的,不是可能抛出。如果要使用throw来抛出受查异常,那throw必须使用在try语句内部或者throws声明的方法中。那为什么第一个例子不符合使用throw的条件呢,因为NullPointException是RuntimeException,是非受查异常,可以选择处理或者不处理。
创建异常类
我们可以自己定义异常类,以便更准确的描述标准异常类无法描述的问题。
package ExceptionTest;
public class MyException extends Exception {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public static void main(String[] args) throws MyException {
throw new MyException("qweeq");
}
自己创建的异常要继承Throwable类,Error不用处理,所以继承Exception就可以,当然如果能更精确就更好。一般来说,异常类的名字要尽量能描述出异常原因,然后声明两个构造器,一个是默认的构造器,一个是带有详细描述信息的构造器(父类Throwable类的toString方法将会打印出这些详细信息,这在调试中非常有用)。
捕获异常
如果发生异常的时候只是抛出没有捕获,那程序就会终止运行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。
想要捕获一个异常,必须使用try/catch语句块
public void read(String filename)
{
try
{
InputStream in = new Filei叩utStream(filename);
int b;
while ((b = in.read()3 != -1)
{
....
}
}
catch (IOException exception)
{
exception.printStackTrace();
}
}
上述代码中的read方法有可能抛出一个IOException异常,那有可能抛出异常的代码段被写在try语句中,如果发生异常,则try语句中代码终止运行,进入catch语句块中处理异常,如果try语句块中抛出了一个catch语句块中没有处理的异常类型那么这个方法会立刻退出(这种情况是应该尽量避免的),当然我们也可以不使用try/catch而是把这个有可能发生的异常用throws抛出,交给上层去处理。
捕获多个异常
try
{
code that might throwexceptions
}
catch (FileNotFoundException e)
{
emergencyactionfor missingfiles
}
catch (UnknownHostException e)
{
emergency actionfor unknown hosts
}
catch (IOException e)
{
emergencyactionfor all other I/O problems
}
捕获多个异常就用多个catch处理,每个catch捕获特定的异常,每个catch的处理方式不同。只有当捕获的异常彼此之间不存在子类关系的时候才需要这个特性。
注意:捕获多个异常时,异常变量e隐含为final变量。
finally子句
当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行,如果方法在执行过程中获取了一些资源,比如数据库连接对象,输入输出流,那我们应该及时关闭它,这时可以使用finally子句,finally的意义是,无论是否发生异常,finally中的代码都将被执行
InputStream in = new FileInputStream(. . .);
try
{
code that might throwexceptions
}
catch (IOException e)
{
showerror message
}
finally
{
in.close();
}
在上面这段代码中,有三种情况会执行finally子句
1.代码没有抛出异常,正常执行完try中代码,然后执行finally语句中代码
2.抛出一个catch语句中捕获的异常,执行try中代码直到发生异常,执行catch中代码,如果没有抛出异常,接着执行finally中代码,如果抛出异常,先等待finally语句执行完成,再将异常抛送给这个方法的调用者
3.catch块未捕获的异常,执行try中代码直到发生异常,将异常抛送给这个方法的调用者,并执行finally中代码(这里可能会有问题,看下面代码)
InputStreai in = ...;
try
{
code that might throw exceptions
}
finally
{
in.close();
}
上面这段代码如果try语句中抛出了一个非IOException类型的异常,那么程序需要执行finally中语句,但是在调用close方法的时候发生了IOException异常,那么try中的异常将丢失,转而抛出close方法的异常,但是实际上我们更需要知道try中发生了什么异常,要处理这种问题,可以用catch捕获想要的异常,然后finally中在使用一次try/catch,捕获close的异常
InputStream in = ...;
Exception ex=null;
try{
try{
code that might throw exceptions
}
catch(Exception e){
throw e;
}
}
finally
{
try
{
in.close();
}
catch(Exception e)
{
if(ex=null)throw e;
}
}
当然还有一种方法,我没怎么用过,带资源的try语句
try(Scanner in=new Scanner(new FileInputStream("7usr/share/dict/words")),"UTF-8")
{
while(in.hasNext())
System.out.println(in.next());
}
这个例子中药读取文件中的所有单词,如果try语句的代码正常执行或者发生异常,最后都会执行in.close,如果要关闭多个资源,就用分号隔开。