文章目录
一、处理错误
异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
可能出现的几种错误:
- 用户输入错误
- 设备错误
- 物理限制(存储)
- 代码错误
1.异常分类
-
RuntimeException∶错误的类型转换、数组访问越界、访问空指针。
-
IOException:试图在文件尾部后面读取数据;试图打开一个不存在的文件;试图根据给定的字符串查找 Class 对象,而这个字符串表示的类并不存在。
-
已检查异常:IOException,编译器将核査是否为所有的已检査异常提供了异常处理器。
-
未检查异常:Error和RuntimeException
2.声明已检查异常
方法应该在其首部声明所有可能抛出的异常。
public f(String name) throws FileNotFoundException
{
……
}
这段代码表示这个方法将根据给定的 String 参数调用方法,但也有可能抛出一个 FileNotFoundException异常。如果发生了这种糟糕情况,方法将无法成功调用,而是抛出一个FileNotFoundException类对象。如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理 FileNotFoundException 对象。
- 总之,一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuftimeException)。也就是说,不需要针对Error和RuntimeException抛出异常,但需要考虑所有可能的IOException异常。
- 如果在子类中覆盖了超类的一个方法,子类方法中声明的已检查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中可以抛出更特定的异常,或者根本不抛出任何异常)。特别需要说明的是,如果超类方法没有抛出任何已检查异常,子类也不能抛出任何已检查异常,但必须捕获方法代码中出现的每一个已检查异常。
3.如何抛出异常
- 步骤:找到一个合适的异常类、创建这个类的一个对象、将对象抛出。
String readData(Scanner in)throws EOFException
{
...
while(...)
{
if(!in.hasNext())
{
if (n <len))
{
String gripe="Content-length:"+len+",Received:"+n;
throw new EOFException(gripe);//抛出异常
}
}
...
}
return s;
}
一旦方法抛出了异常,这个方法就不可能返回到调用者。也就是说,不必为返回的默认值或错误代码担忧。
4.创建异常类
我们需要做的只是定义一个派生于 Exception的类,或者派生于Exception子类的类。例如,定义一个派生于IOException 的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类 Throwable 的toString 方法将会打印出这些详细信息,这在调试中非常有用)。
二、捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。
下面是处理异常的两种方式:
- 第一种:如果不知道怎么处理异常,就将异常传递给调用者。
String readData(Scanner in)throws EOFException
{
...
while(...)
{
if(!in.hasNext())
{
if (n <len))
{
String gripe="Content-length:"+len+",Received:"+n;
throw new EOFException(gripe);//抛出异常
}
}
...
}
return s;
}
- 第二种:如果知道怎么处理,就可以自己处理一下
public void read(String filename)
{
try
{
InputStream in= new FileInputStream(filename));
int b;
while((b =in.read())!=-1)
{
process input
}
}
catch (IOException exception)
{
exception.printStackTrace();
}
}
仔细阅读一下 Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是添加到 throws 列表中。对于后一种情况,也不必犹豫。将异常直接交给能够胜任的处理器进行处理要比压制对它的处理更好。
但是,对于throws的使用是有限制的。前面曾经提到过∶如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个已检查异常。不允许在子类的throws 说明符中出现超过超类方法所列出的异常类范围。
1.捕获多个异常
通过使用多个catch子句实现:
try
{
...
}
catch()
{
...
}
catch()
{
...
}
catch()
{
...
}
e.getMessage();//获得更详细的错误信息
e.getClass().getName();//得到异常的实际类型
可以合并具有相同动作的catch子句,但异常类型之间不能存在子类关系,而且异常变量隐含为final变量:
try
{
...
}
catch(FileNotFoundException|UnknownHostException e)
{
...
}
catch()
{
...
}
catch()
{
...
}
2.再次抛出异常与异常链
try
{
...
}
catch(SQLException e)
{
Throwable se = new ServletException("database error");
se.initCause(e);
throw se;
}
当捕获到异常时,就可以使用下面这条语句重新得到原始异常∶
Throwable e = se.getCause();
有时你可能只想记录一个异常,再将它重新抛出,而不做任何改变∶
try
{
}
catch(Exception e)
{
logger.log(level,message,e);
throw e;
}
3.finally子句
当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。使用finally子句可以解决这个问题。
不管是否有异常被捕获,finally 子句中的代码都被执行。
try
{
...
}
catch()
{
...
}
finally
{
...
}
警告∶ 当 finally 子句包含 return 语句时,将会出现一种意想不到的结果。假设利用 retur语句从 try 语句块中退出。在方法返回前,finally 子句的内容将被执行。如果 finally子句中也有一个 return 语句,这个返回值将会覆盖原始的返回值。
4.带资源的try语句
try(Scanner in= new Scanner(new FileInputStream"/usr/share/dict/words"),
Printwriter out = new PrintMriter("out.txt")))
{
while (in.hasNext())
{
out.println(in.nextO.toUpperCase();
}
}
不论这个块如何退出,in和out 都会关闭。
5.分析堆栈跟踪元素
具体请看Throwable接口的API。