9.1 基本违例
结合文中所说可以推断出这里的“基本违例”特指的是运行时异常,解决的办法是将这些异常向上抛,即文中所讲“将那个问题委托给一个更高级的负责人”,这里记录一下运行时异常发生时的处理机制:在堆内存中创建违例对象,停止当前执行路径,释放违例对象句柄,违例机制接管并查找合适的地方(指违例控制器,即catch()
块)用于程序的执行。
9.1.1 违例自变量
违例自变量作为一个Java对象,使用时调用其构造器(两种),默认空构造器和带参构造器,以空指针异常为例对应下面的代码:
//默认构造器
throw new NullPointerException();
//带参构造器
throw new NullPointerException("出现3异常");
9.2 违例的捕获
通过文章的阅读,上述的违例控制器实际指的就是catch(xxException e)
部分的代码块,在这一块中,存在两种基本方法:一种叫“中断”,只要抛出一个异常就表明无法补救该异常,也无法返回异常发生的地方,程序终止运行;另一种叫“恢复”,表示违例控制器得到控制后仍想继续执行(发生异常后假定下一次会会执行成功不会发生异常),继而调用一个解决问题的方法。在捕获违例时,定义的控制器带参类型必须遵照“上小下大”(上面的Exception是下面Exception的子类)的原则,否则后面的控制器将失效,这一点在9.2.5小节中的ThrowOut
案例中写的很复杂(第一次看根本不知道它在说什么,可能是翻译的问题),但总结起来就是上述的“上小下大”的规则。在程序调试时需要打印一些异常信息,可以使用e.printStackTrace()
方法(这个方法在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 Throwable {
try {
f();
} catch (Exception e) {
System.out.println("Inside g(), e.printStackTrace()");
e.printStackTrace();
throw e;
// throw e.fillInStackTrace();
}
}
public static void main(String[] args) throws Throwable{
try {
g();
} catch (Exception e) {
System.out.println("Caught in main, e.printStackTrace()");
e.printStackTrace();
}
}
}
上述程序中涉及到throw
(方法内部使用)和throws
(方法体上使用)的使用,在异常不断向上抛出时,我们可以看出原始的异常是在f()
方法中抛出,在简单的使用throw e;
向上抛出的过程中,异常对象的相关信息都会得到保留,比如初始异常抛出的位置,在g()
中可以得到初始异常在方法f()
中抛出,在main()
方法中同样得到异常在f()
中抛出,当然也可以重新安装这些位置信息,使用throw e.fillInStackTrace();
,这样在g()
可以得到异常从f()
抛出,但由于在g()
中重抛时使用了throw e.fillInStackTrace()
(装载了g()
的位置信息),那此时在main()
捕获的异常信息是只能追踪到g()
而不能追踪到f()
。
9.3 标准Java违例
Throwable
是“万恶之源”,包含了Error
和Exception
,前者是用于处理程序运行环境方面的错误,通常和硬件相关,不管程序怎么调试,这种错误都不会消失;后者是因为程序本身引发的异常,通常通过调试可以解决。在这一小节中作者提出一个问题:
如果不捕获这些违例,又会出现什么情况?
为此给出下面的程序:
public class NeverCaught {
static void f() {
throw new RuntimeException("From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
}
第一反应如果出现异常不捕获,那么此时程序终止运行并打印异常,作者给出的答案是:
假若一个RuntimeException 获得到达main()的所有途径,同时不被捕获,那么当程序退出时,会为那个违例调用 printStackTrace()。
作者这句话是否就是我理解的程序终止运行并打印异常,但是这句话又提到了2个条件:第一,异常获得到达main()
的所有途径;第二,异常不被捕获。对于第二个条件不需要解释,第一个条件是什么意思呢,到达main()
的所有途径,如果是简单的函数调用,如下:main()调用f1(),f1()调用f2(),f2()调用f3()。那每一个函数f1()
、f2()
和f3()
都可以获得到到达main()
的途径,如果要所有途径,那这里特指最后一个函数f3()
,在上述程序就是指f()
函数,那么就是说g()
函数中的异常不会调用printStackTrace()
,但是在将上述的抛出语句移至g()
中,程序同样调用了printStackTrace()
是什么原因呢?
9.4 创建自己的违例
这里其实很简单,继承Exception
或者RuntimeException
,重写其中的构造器(可以是部分)即可,当然可以根据自身需求进行合理改造,这里个人觉得书中的案例可看,但不如下面这个栗子好看,不但给出了基本用法,还提供了比较合适的使用场景:
class FSException extends RuntimeException {// getMessage
private int value;
FuShuException() {
super();
}
FuShuException(String msg, int value) {
// TODO Auto-generated constructor stub
super(msg);
this.value = value;
}
public int getValue() {
return value;
}
}
class Demo {
int div(int a, int b) {
if (b < 0) {
throw new FSException("出现了除数是负数的情况/by 负数", b);// 手动通过throw抛出异常
}
if (b == 0) {
throw new ArithmeticException("被0除了");
}
return a / b;
}
}
public class ExceptionByIden {
public static void main(String[] args) {
Demo demo = new Demo();
int x;
try {
x = demo.div(4, -1);
System.out.println("x = " + x);
} catch (FSException e) {
System.out.println(e.toString());
System.out.println("除数出现负数");
System.out.println("错误的负数是:" + e.getValue());
} catch (Exception e) {
System.out.println(e.toString());
}
System.out.println("over");
}
}
9.5 违例的限制
这一块看的比较模糊,书中讲的不多,看得懂每句话,但不清楚这一小节核心思想在哪里,个人理解下来,是在谈论父类、接口和其子类以及实现类中方法重写过程中异常的捕获问题。首先对受检异常(checked exception)和非受检异常(unchecked exception)做一个通用的说明:
- 非受检异常:
Error
和RuntimeException
(及其子类如空指针异常、数组越界异常、算术异常等称为未检查异常unchecked,或非受检异常); - 受检异常:其他异常(如
Exception
、FileNotFoundException
、IOException
、SQLException
等)称为已检查异常checked(或者受检异常),
通过资料的查阅和实操,对于子类和父类(或者接口和其实现类)之间方法重写过程中的异常限制有如下总结:
- 如果父类方法中抛出的异常是非受检异常,那么子类重写该方法时只可以不抛该异常或者抛该异常或它的子类异常;
- 如果父类方法中抛出的异常是受检异常,那么子类重写该方法时可以根据第一条规则处理,此外,它还可以抛非受检异常;
- 子类实现或继承后,对于相同签名的方法,要么不抛异常,要么抛出相同签名方法抛出异常的交集;
这么理解下来就很简单了,记录一下运行代码:
public class BaseballException extends Exception {}
public class Strike extends BaseballException {}
public class Foul extends BaseballException {}
public class PopFoul extends Foul {}
public class StromException extends Exception {}
public class RainedOut extends StromException {}
interface Strom {
void event() throws RainedOut;
void rainHard() throws RainedOut;
}
abstract class Inning {
Inning() throws BaseballException {}
void event() throws BaseballException {}
abstract void atBat() throws Strike, Foul;
void walk() {}
}
public class StromInning extends Inning implements Strom {
StromInning() throws RainedOut, BaseballException {}
StromInning(String s) throws Foul, BaseballException {}
//父类方法抛出Strike, Foul,子类方法抛出异常PopFoul是Foul的子类
@Override
void atBat() throws PopFoul {
}
//Inning和Strom都有该方法,但BaseballException和RainedOut没有交集,所以不抛异常
@Override
public void event() {
}
//直接抛父类异常(或子类)或者不抛
@Override
public void rainHard() throws RainedOut {
}
public static void main(String[] args) {
try {
StromInning si = new StromInning();
si.atBat();
} catch (PopFoul e) {
} catch (RainedOut e){}
catch (BaseballException e) {
}
try {
Inning i = new StromInning();
i.atBat();
} catch (Strike strike) {
} catch (Foul foul) {
} catch (RainedOut e) {
}catch (BaseballException e) {
}
}
}
9.6 finally
这一块没什么东西,正常理解即可,通常情况下使用finally
做资源的清理、关闭工作,总而言之finally
块肯定会在最后执行。文中在9.6.2小节处提到一个注意点,就是使用finally
块可能会造成异常的丢失,并给出案例:
public class LostMessage {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args) throws Exception {
LostMessage lm = new LostMessage();
try {
lm.f();
} finally {
lm.dispose();
}
}
}
最后控制台打印的信息显示确实丢失了f()
中的VeryImportantException
,但是个人觉得这一块使用try-catch
完全可以控制这一问题,在实际开发过程成,本人finally
块中也确实一直都是使用try-catch
来完成异常的解决,而不是将异常笼统的向上抛(虽然这样可以在一定程度上简化我们的工作量,这一点在9.7章节作者做出)。
9.7 构建器
这一小节实在没有理清楚作者想表达什么……对象的创建?还是资源的销毁?
9.8 违例匹配
该小节其实就是在讲之前总结的控制器“上小下大”的原则。