通过异常处理错误
Java的基本理念是”结构不佳的代码不能运行”
- 改进错误恢复机制是提供代码健壮性的强有力的方式.
- java中异常处理的目的在于通过使用少于目前数量的代码来简化大型 可靠的程序的生成,并且通过这种方式可以使你增加自信.
- 异常处理时Java中唯一正式的错误报告机制,并且通过编辑器强制执行.
12.1基本概念
- 异常
- 问题出现了只是在当前环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中.
- 另一个好处是,它往往能够降低错误处理代码的复杂度.如果不适用异常,那么就必须见检查特定的错误,并在程序中的许多地方去处理它.如果使用异常那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误.即所谓的异常处理程序中.
12.2基本异常
- 异常情形(exception condition)是阻止当前方法或作用域继续执行的问题.
- 所有标准异常类都有两个构造器:一个是默认构造器;另一个是接收字符串作为参数,以便能把相关信息放入异常对象的构造器.
- 关键字throw在使用new创建了异常对象之后,此对象的引用将传给throw.从效果上看,它就像是从方法”返回”的.(但并不要深究),另外还能用抛出异常的方式从当前的作用域退出.
12.3捕获异常
- catch捕获异常,catch块里面就是异常处理程序.
- 终止与恢复模型
- 终止模型:在这种模型中,假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行.一旦异常被抛出,就表名错误已无法返回,也不能回来继续执行.
- 恢复模型:意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法.并认为第二次能成功.
- 恢复模型开始很吸引人但不是很实用.其主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码.这增加了维护的难度.
12.4创建自定义异常
- 要自己定义异常类,必须从已有的异常类继承 最好是选择意思相近的异常类继承(不过不好找)
- 建立新的异常类型最简单的方法就是让编辑器为你产生默认构造器(对异常来说,最重要的部分就是类名)所以这样的异常类在大多数情况下足够了.
- System.out标准输出流,System.err标准错误流,System.in输入流所以Scanner(System.in);
- 在异常处理程序中,调用了在Throwable类声明(Exception即从此类继承)的printStackTrace()方法.它将打印”从方法调用处直到异常抛出处”(弹栈顺序)的方法调用序列.
12.5异常说明
- 异常说明使用了附加的关键字throws,后面接一个所有潜在一场类型的列表(强制性的检查)
- 代码必须与异常说明保持一致.
- 这种在编译时被强制检查的异常被称为被检查的异常.
12.6捕获所有异常
- 通过捕获异常类型的基类Exception,就可以做到捕获所有异常类型的异常.
- 最好把Exception放在处理程序列表末尾,以防它请在其他处理程序之前把异常捕获了.
- 因为Exception是编程有关的所有异常的基类,所以它不会含有太多具体的信息,不过可以调用它从其基类Throwable继承的方法:
- String getMessage()
- String getLocalizedMessage()用来获取详细信息,或用本地语言表示详细信息.
- String toString()返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内.
- void printStackTrace()
- void printStackTrace(PrintStream)
- void printStackTrace(java.io.PrintWriter) 打印Throwable和Throwable的调用栈轨迹.调用栈显示了”把你带到异常抛出地点”的方法调用序列.
- 后两个版本允许选择要输出的流
- Throwable fillInStackTrace()用于在Throwable对象的内部记录栈帧的当前状态.这在程序重新抛出错误或异常时会很有用
- 栈轨迹 printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中元素所构成的数组,其中每一个元素都表示栈中的一帧.元素0时栈顶元素,并且时调用序列中的最后一个方法调用(这个Throwable被创建和抛出之处).
- 重新抛出异常 catch(Exception e){System.out.println(“An exception was thrown”); throw e;}
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块后续catch句子将被忽略.此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息.
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来抛出点的调用栈信息,而并非重新抛出点的信息.想要更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的
public class Rethrowing {
public static void f() throws Exception {
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();//因为返回的是一个Throwable对象所以这是一个向下类型转换
}
}
public static void main(String[] args) {
try {
g();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
//下面这一段
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.h(Rethrowing.java:20)
at Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
//和下面这一段的区别
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:24)
at Rethrowing.main(Rethrowing.java:35)
*///:~
- 异常链 :常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链.
12.7Java标准异常
- Throwable这个Java类被用来表示任何可以作为异常被抛出的类.
- Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误;Exception是可以被抛出的基本类型.
- 特例RuntimeException
public class NeverCaught{
static void f(){
throw new RuntimeException("From f()");
}
static void g(){
f();
}
public static void main(String[] args){
g();
}
}/*Exception in thread "main" java.lang.RuntimeException: From f()
at cn.itcast.zhuofai806.demo12_7.NeverCaught.f(NeverCaught.java:5)
at cn.itcast.zhuofai806.demo12_7.NeverCaught.g(NeverCaught.java:8)
at cn.itcast.zhuofai806.demo12_7.NeverCaught.main(NeverCaught.java:11)
*///~
我们可以发现,RuntimeException(或任何从它继承的异常)是一个特例.对于这种异常类型,编译器不需要异常声明,其输出被报告给了System.err
所以答案是:如果RuntimeException没有被捕获而直达main(),那么程序退出前将调用异常的printStackTrace()方法.
请务必记住:只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的.究其原因,RuntimeException代表的是编程错误:
1.无法预料的错误
2.作为程序员,应该在代码中进行检查的错误.
12.8使用finally进行清理
- 对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要.但java有垃圾回收机制,所以内存释放不再是问题.而且,java也没有析构函数可供调用,当要把出内存之外的资源恢复到它们初始状态时,就要用到finally子句.这种需要清理的资源.
- 当涉及break和continue语句的时候,finally子句也会得到执行.请注意如果把finally子句和带标签的break及continue配合使用,在Java里就没必要使用goto语句了.
- 缺憾:异常丢失
class VeryImportantException extends Exception{
public String toString(){
return "A very important exception!";
}
}
class HoHumException extends Exception{
public String toString(){
return "A trivial exception";
}
}
public class LostMessage {
void f()throws VeryImportantException{
throw new VeryImportantException();
}
void dispose() throws HoHumException{
throw new HoHumException();
}
public static void main(String[] args) {
try {
LostMessage lm = new LostMessage();
try {
lm.f();//这个try后面没有catch语句进行捕捉而后面finally中的dispose又重新抛出一个异常覆盖.
}finally {
lm.dispose();
}
}catch (Exception e){
System.out.println(e);
e.printStackTrace(System.out);
}
}
}
12.9异常的限制
//因为异常的限制包括在方法和上面的声明的里面,所以没有把main函数写到代码中.
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
public Inning() throws BaseballException {}
public void event() throws BaseballException {
// Doesn't actually have to throw anything
}
public abstract void atBat() throws Strike, Foul;
public void walk() {} // Throws no checked exceptions
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm {
// OK to add new exceptions for constructors, but you
// must deal with the base constructor exceptions:
public StormyInning()
throws RainedOut, BaseballException {}
public StormyInning(String s)
throws Foul, BaseballException {}
// Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//! public void event() throws RainedOut {}
// If the method doesn't already exist in the
// base class, the exception is OK:
public void rainHard() throws RainedOut {}
// You can choose to not throw any exceptions,
// even if the base version does:
public void event() {}
// Overridden methods can throw inherited exceptions:
public void atBat() throws PopFoul {}
代码剖析
- 派生类构造器的异常说明必须包含基类构造器的异常说明.此外派生类的构造器可以抛出任何异常(包括不在基类的异常)
- 派生类构造器不能捕捉基类构造器抛出的异常.
- 即StormyInning.walk()不能通过编译的原因是因为:它抛出了异常,而Inning.walk()并没有声明此异常.如果编译器允许这么做的话,当把它替换成Inning的派生类对象时,这个方法就有可能抛出异常,于是程序就失灵了.
- 覆盖后的方法可以不抛出任何异常.
- 如果要在main函数中处理类对象的话,编译器只会强制你捕获基类的异常,如果向上转型为基类,那么编译器就会要求你捕获基类的异常(基类的异常包含派生类的异常))
12.10 构造器
//对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式时使用嵌套的try子句
public class Cleanup {
public static void main(String[] args) {
try {
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while((s= in.getLine()) != null);
}catch (Exception e){
System.out.println("Caught Exception in main");
e.printStackTrace();
}finally {
in.dispose();
}
}catch (Exception e){
System.out.println("InputFile construction failed");
}
}
}
程序逻辑:对InputFile对象的构造器在其自己的try语句块中有效,如果构造失败,将进入外部的catch子句,而dispose()方法不会被调用.但是如果构造成功,我们肯定想确保对象能够被清理,因此,在构造之后立即创建了一个新的try语句块.执行清理的finally与内部的try语句块相关联.在这种方式中,finally子句在构造失败时是不会执行的,而在构造成功时总是执行.
注意:如果dispose()可以抛出异常,那么你可能需要额外的try语句块.基本上你应该仔细考虑所有的可能性,并确保正确处理每一种情况.
12.11异常匹配