程序中保证错误出现的时候至少做到:
————————————————————————————
- 向用户通知错误
- 保存所有的工作
- 允许用户妥善的退出程序
————————————————————————————
一般要考虑的问题:
————————————————————————————
- 用户输入错误
- 设备错误
- 物理限制
- 代码错误
————————————————————————————
异常分类
在java中,异常对象都是派生于Throwable类的一个类实例。
另外,用户可以自定义自己需要的异常类
层次结构
Thorwable
/ \
Error Exception
/ \
IOException RuntimeException
派生于RuntimeExpetion的异常包括以下问题:
- 错误的类型转换
- 数组访问越界
- 访问null指针
不是派生于RuntimeException的异常包括:
- 试图超过文件末尾读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
————————————————————————
通过检测数组下标是否越界来避免ArrayIndexOutOfBoundsException异常
通过检测变量是否为null来杜绝NullPointerException异常
————————————————————————
异常规范
java规范将派生于Error类或RuntimeException类的所有异常称为非检查型异常,其他称为检查型异常
异常处理
1.抛出异常
如何抛出异常?
读取文件时抛出异常
String readData(Scanner in) thorws EOFException
{
...
while (...)
{
if (!in.hashNext())//EOFException
{
if(n<len)throw new EOFException();
}
...
}
return s;
}
EOFException异常类还有一个带字符串参数的构造器。可以利用这个构造器更详细的描述异常:
String gripe = "Content-length:"+len+",Received:"+n;
throw new EOFException(gripe);
- 在java里,只能抛出Throwable子类的对象,而在C++中可以抛出任何类型的值
创建需要的异常类
遇到已有的标准异常类难以解决的问题,在这种情况下需要构建属于自己的异常类。一般异常类派生于Throwable类及其子类。
- 一般给这种自定义类安排至少两个构造器,一个是默认的构造器,另一个是描述详细信息的构造器(超类Throwable的toString方法返回一个字符串,其中包含详细信息,这在调试中常常很有效
class FileFormatException extends IOException
{
public FileFormatException(){}
public FileFormatException(String gripe)
{
super(gripe);
}
}
-
Thorwable()
创建一个新的Throwable对象且不添加描述 -
Throwable(String message)
创建一个新的Throwable对象,使用一个字符串对其进行描述 -
String getMessage()
获得Throwable 对象的详细描述信息
——————————————————————————
异常捕获
捕获异常的结构:
try
{
code
}
catch(ExceptionType e)
{
handler for this type
}
-
如果try子句长得任何代码抛出了catch中指定的异常类型
,则:
1 程序将跳过try语句块的其余代码
2 程序将执行catch子句中的处理器代码 -
如果try子句中跑出了catch中没有指定的异常,则这个方法会直接退出(可能会向上一级调用者需求处理的子句)
-
如果try子句中没有抛出任何异常,则程序会直接跳过catch子句
一般比较好的方法是让调用者对将会出现的异常进行处理,而本方法只提供一个提示:
public void read(String filename)throws IOException
{
var in = new FileInputStream(filename);
int b;
while (b = in.read()!=-1)
{
process input
}
}
throws IOException
该语句向它的调用者提供提醒,告知其这个方法可能会产生一个
IOException异常
继承关系中:
- 超类的方法中没有声明要抛出的异常,在子类中需要亲自捕获
- 不允许在子类的throws说明符中出现超类未定义的异常类!
异常类型的分析:使用 e.getMessage()得到详细的错误信息
或者使用e.getClass().getName()得到对象的实际类型
多个异常的捕获
在原本的Java版本中捕获多个异常是这样的:
try
{
code
}
catch (FileNotFoundException e)
{
code
}
catch(IOException e)
{
code
}
java7中为这种多个异常捕获的情况提供了方法:
try
{
code
}
catch(FileNotFoundException|UnknownHostException e)
{
}
变量e隐含为final变量
catch(IoException e)
{
}
只有当一起捕获的两个异常类之间不存在继承关系的时候才需要这个特性。捕获多个异常会使得程序更高效,但是处理过程很复杂的话需要另行考虑。
二次的异常抛出以及异常链
可以在catch子句中抛出一个异常。一般用于:
- 希望改变异常的类型
- 指示下级处理是系统故障类型
可以如下捕获异常并且再次抛出
try
{
code
}
catch(SQLException e)
{
throw new SerleException("database error :"+e.getMessage());
}
更好的一种处理方法,将原始异常设置为新异常的"原因"
try
{
code
}
catch (SQLException original)
{
var e = new ServletException("database error");
e.initCause(original);
throw e;
}
捕获到这种异常以后,就可以使用下面语句获取原始异常:
try
{
code
}
catch(Exception e)
{
logger.log(level,message,e);
throw e;
}
有时候用来记录一个代码并且重新抛出:
try
{
code
}
catch (Exception e)
{
logger.log(level,message,e);
throw e;
}
finally子句
代码抛出一个异常时,就会终止所有正在进行的方法内工作,并且退出这个方法。可以在finally子句中完成后继操作,比如剩余未释放资源的清理等。
finally的执行无关是否有对应的异常被捕获
在下面的范例中,无论发生什么,最终程序都将关闭输入流
var in = new FIleInputStream(...);
try
{ 1
code
2
}
catch(IOException e)
{ 3
show error message
4
}
finally
{ 5
in.close();
6
}
在这个结构中:
-
代码没有抛出异常:1256
-
代码抛出一个异常,并且在一个catch中被捕获:
1 catch中没有抛出异常:13456
2 catch子句中抛出了一个异常,返回到方法调用者处:135 -
代码抛出一个异常,但没有任何子拒捕获异常:15
————————————————————————
try语句允许没有catch只有finally,无论是否遇到异常,finally都会正常执行,然后一般这个异常会被再次抛出。如下:
InputStream in = ....;
try
{
try
{
code
}
finally
{
in.close();
}
}
catch(IOException e)
{
show error message
}
此处内部的try语句块只有一个作用,那就是确保关闭输入流。外层
的try也只有确保报告处理出现的错误。
上述的结构清楚且功能更强:对finally子句也进行了检查!
如果在finally中设置了return值,由于是try中先return,返回前经过finally再返回!这样就会导致返回值被覆盖
如上,一般不应该在finally中出现(return 。throw。break。continue)
要想检测finally中错误用上面的结构就好
try-with-resources(带资源的try语句)
对于如下结构:
open a resource
try
{
work with the resource
}
finally
{
close the resource
}
假设resource资源属于一个实现了AutoCloseable接口的类:
try(resources)
{
work with resources
}
该结构会在结束时自动对所有的资源调用close();
例子:
try(var in= new Scanner(new FileInputStream
("/usr/share/dict/words"),StandardCharsets.UTF_8);
var out = new PrintWriter("out.txt"),StandardCharsets.UTF_8))
{
while(in.hasNext())
out.println(in.next().toUpperCase());
}
只要需要关闭资源,就最好使用这种方法,有意思的是,这种try同样可以匹配catch和finally子句。
堆栈轨迹
书p294
使用异常的技巧
- 异常处理不能替代简单的测试
与普通的直接判断相比,经常地异常处理会有更长的运行时间 - 不要过分细化异常
通常将整个任务放在同一个try块中而不是对每一句都try测试 - 充分利用异常层次结构
- 不要压制异常,如果认为可能出现的异常很重要,就要进行相应的处理
- 检测错误的时候一般要严格,宁杀错千人不放过一个
- 捕获异常最好在高层,即从底层一步步传上来再处理
- 总结56 得出**“早抛出,晚捕获”**
一般情况下输入判断使用一般测试而非异常处理,这会优化运行的时间