第12章 通过异常处理错误
java的基本理念是”结构不佳的代码不能运行“
通过这种方式可以使得你更加自信:你的应用中没有未处理的错误。异常处理时Java中唯一正式的错误报告机制,并且通过编译器强制执行。
12.1 概念
把”描述再正常执行过程中做什么事“的代码和”出了问题怎么办的代码相分离“异常使代码相分离。
12.2 基本概念
当抛出异常后
- 使用new再堆上创建异常对象
- 当前执行路径(他不能继续下去)被终止,并且从当前环境中弹出对异常对象的引用
- 异常处理机制接管程序
- 寻找一个恰当的地方(异常处理程序)继续执行程序
- 将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去
12.2.1 异常参数
用new再堆上创建异常对象,伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:
- 一个是默认构造器
- 另一个接受字符串为参数,以便能把相关信息放入异常对象的构造器
Throw new NullPointerException("t = null");
new创建了异常对象之后,此对象的引用将传给throw。尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像使从方法”返回“的。
12.3 捕获异常
监控区域(guarded region)的概念
12.3.1 try块
方法内部抛出异常(方法内部调用其他的方法抛出了异常)
12.3.1 异常处理程序
紧跟在try后,以关键字catch表示(看起来就像是接受一个且仅接受一个特殊类型的方法 )
-
当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序
-
然后进入catch子句执行,此时认为异常得到了处理
-
catch子句结束,处理程序的查找过程结束
-
只有匹配的catch才能执行
终止与恢复
异常处理理论上有两种基本模型。
- Java支持终止模型(它是Java和C++所支持的模型)。在这种模型中,一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
- 恢复模型。异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。
- 如果Java像实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误,或者把try放进while循环里,这样就不断地进入try块,直到得到满意的结果。
- 恢复性异常 导致耦合,恢复性程序需要了解异常抛出的地点 , 这势必要包含依赖于抛出位置的非通用性代码。
12.4 创建自定义异常
自己定义异常类来表示程序中可能遇到的特定问题,最好是选择意思相近的异常类继承。
练习1
/* Create a class with a main(0 that throws an object of class Exception
* inside a try block. Give the constructor for Exception a String argument.
* Catch the exception inside a catch clause and print the String argument.
* Add a finally clause and print a message to prove you were there.
*/
class Exception1 extends Exception {
public Exception1(String msg) {
super(msg);//super构造基类构造器
System.out.println("Exception1(String msg)");
}
}
public class No1Ex {
public static void f() throws Exception1 {
System.out.println("Throwing MyException from f()");
throw new Exception1("From f()");
}
public static void main(String[] args) {
try {
f();
} catch(Exception1 e) {
System.err.println("Caught Exception1");
e.printStackTrace();
} finally {
System.out.println("Made it to finally");
}
}
}
=============================================================
Throwing MyException from f()
Exception1(String msg)
Made it to finally
Caught Exception1
第十二章异常处理.Exception1: From f()
at 第十二章异常处理.No1Ex.f(No1Ex.java:18)
at 第十二章异常处理.No1Ex.main(No1Ex.java:22)
练习2
/* Define an object reference and initialize it to null. Try to call a method
* through this reference. Now wrap the code in a try-catch clause to catch the
* exception.
*/
public class No2Ex {
private static Integer i = null;
public static void main(String[] args) {
// leads to NullPointerException:
// System.out.println(i.toString());
try {
System.out.println(i.toString());
} catch(NullPointerException e) {
System.err.println("Caught NullPointerException");
e.printStackTrace();
}
try {
i = 10;
System.out.println(i.toString());
} catch(NullPointerException e) {
System.err.println("Caught NullPointerException");
e.printStackTrace();
} finally {
System.out.println("Got through it");
}
}
}
=======================================================================
Caught NullPointerException
java.lang.NullPointerException
at 第十二章异常处理.No2Ex.main(No2Ex.java:14)
10
Got through it
练习3
package 第十二章异常处理;
// Write code to generate and catch an ArrayIndexOutOfBoundsException.
public class No3Ex {
private static int[] ia = new int[2];
public static void main(String[] args) {
try {
ia[2] = 3;
} catch(ArrayIndexOutOfBoundsException e) {
System.err.println(
"Caught ArrayIndexOutOfBoundsException");
e.printStackTrace();
}
}
}
=========================================================================
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 2
at 第十二章异常处理.No3Ex.main(No3Ex.java:8)
练习4
/* Create your own exception class using the extends keyword. Write a
* constructor for this class that takes a String argument and stores it inside
* the object with a String reference. Write a method that displays the stored
* String. Create a try-catch clause to exercise your new exception.
*/
class Exception4 extends Exception {
private String msg;
Exception4(String msg) {
super(msg);
System.out.println("Exception4()");
this.msg = msg;
}
protected void showS() {
System.out.println("Message from Exception4: " + msg);
}
}
public class No4Ex {
public static void f() throws Exception4 {
System.out.println("f()");
throw new Exception4("Ouch from f()");
}
public static void main(String[] args) {
try {
f();
} catch(Exception4 e) {
System.err.println("Caught Exception4");
e.printStackTrace();
e.showS();
}
}
}
=============================================================f()
Exception4()
Message from Exception4: Ouch from f()
Caught Exception4
第十二章异常处理.Exception4: Ouch from f()
at 第十二章异常处理.No4Ex.f(No4Ex.java:24)
at 第十二章异常处理.No4Ex.main(No4Ex.java:28)
练习5
/* Create you own resumption-like behavior using a while loop that repeats
* until an exception is no longer thrown.
*/
public class No5Ex {
private static int[] ia = new int[2];
static int x = 5;
public static void main(String[] args) {
while(true) {
try {
ia[x] = 1;
System.out.println(ia[x]);
break;
} catch(ArrayIndexOutOfBoundsException e) {
System.err.println(
"Caught ArrayIndexOutOfBoundsException");
e.printStackTrace();
x--;
} finally {
System.out.println("Are we done yet?");
}
}
System.out.println("Now, we're done.");
}
}
========================================================================
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 5
at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 4
at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 3
at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 2
at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Are we done yet?
Are we done yet?
Are we done yet?
Are we done yet?
1
Are we done yet?
Now, we're done.
12.4.1 异常与记录日志
使用 java.util.logging 工具将输出记录到日志中。
LoggingException将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便。
练习6
/* Create two exception classes, each of which performs its own logging
* automtically. Demonstrate that these work.
*/
import java.util.logging.*;
import java.io.*;
class Oops1 extends Exception {
private static Logger logger = Logger.getLogger("LoggingException");//与错误相关的包名或者类名字
public Oops1() {
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));//获取抛出处的栈轨迹,接受Java。io。PrintWriter对象作为参数从而产生字符串
logger.severe(trace.toString());//severe 直接调用与日志级别相关联的方法
}
}
class Oops2 extends Exception {
private static Logger logger = Logger.getLogger("LoggingException");
public Oops2() {
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
public class No6Ex {
static void f() throws Oops1 {
throw new Oops1();
}
static void g() throws Oops2 {
throw new Oops2();
}
public static void main(String[] args) {
try {
f();
} catch(Exception Oops1) {}
try {
g();
} catch(Exception Oops2) {}
}
}
=======================================================================
二月 16, 2021 9:44:22 下午 第十二章异常处理.Oops1 <init>
严重: 第十二章异常处理.Oops1
at 第十二章异常处理.No6Ex.f(No6Ex.java:29)
at 第十二章异常处理.No6Ex.main(No6Ex.java:36)
二月 16, 2021 9:44:22 下午 第十二章异常处理.Oops2 <init>
严重: 第十二章异常处理.Oops2
at 第十二章异常处理.No6Ex.g(No6Ex.java:32)
at 第十二章异常处理.No6Ex.main(No6Ex.java:39)
练习7
// Modify Exercise 3 so that the catch clause logs the result.
import java.util.logging.*;
import java.io.*;
public class No7Ex {
private static int[] ia = new int[2];
private static Logger logger = Logger.getLogger("Ex7 Exceptions");
static void logException(Exception e) { // Exception e argument
StringWriter trace = new StringWriter();
e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
public static void main(String[] args) {
try {
ia[2] = 3;
} catch(ArrayIndexOutOfBoundsException e) {
System.err.println(
"Caught ArrayIndexOutOfBoundsException");
e.printStackTrace();
// call logging method:
logException(e);
}
}
}
====================================================================
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 2
at 第十二章异常处理.No7Ex.main(No7Ex.java:18)
二月 16, 2021 10:04:45 下午 第十二章异常处理.No7Ex logException
严重: java.lang.ArrayIndexOutOfBoundsException: 2
at 第十二章异常处理.No7Ex.main(No7Ex.java:18)
12.5 异常说明
Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。
Java使用了一种语法,使你能以礼貌的方式告知客户端程序员某个方法可能会抛出异常 ,然后客户端程序员就可以进行相应的处理。这样异常说明。它属于方法声明的一部分,紧跟在形式参数列表之后。
练习8
/* Write a class with a method that throws an exception of the type created
* in Exercise 4. Try compiling it without an exception specification to see
* what the compiler says. Add the appropriate exception specification. Try
* out your class and its exception inside a try-catch clause.
*/
class Test8 {
public static void f() throws Exception4 {
System.out.println("f()");
throw new Exception4("Ouch from f()");
}
}
public class No8Ex {
public static void main(String[] args) {
try {
Test8 t = new Test8();
t.f();
} catch(Exception4 e) {
System.err.println("Caught Exception4");
e.printStackTrace();
e.showS();
}
}
}
============================================================
f()
Exception4()
Message from Exception4: Ouch from f()
Caught Exception4
第十二章异常处理.Exception4: Ouch from f()
at 第十二章异常处理.Test8.f(No8Ex.java:13)
at 第十二章异常处理.No8Ex.main(No8Ex.java:21)
12.6 捕获所有异常
捕获异常的基类Exception
放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了
Exception没有太多的具体信息,可以调用它的基类Throwable继承方法:
String getMessage()
String getLocalizeMessage()
来获取详细信息,或用本地语言表示详细信息。
String toString()
返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内:
void printStrackTree() 输出到标准错误
void printStackTree(PrintStream) 选择PrintStream为输出流
void printStackTree(java.io.PrintWriter) 选择Jav.io.PrintWriter 为输出流
打印Throwable和Throwable的调用栈轨迹”把你带到异常抛出点“的方法调用序列
练习9
/* Create three new types of exceptions. Write a class with a method that
* throws all three. In main(), call the method but only use a single catch
* clause that will catch all three types of exceptions.
*/
class ExceptionA extends Exception {
ExceptionA(String msg) { super(msg); }
}
class ExceptionB extends Exception {
ExceptionB(String msg) { super(msg); }
}
class ExceptionC extends Exception {
ExceptionC(String msg) { super(msg); }
}
public class No9Ex {
public static void f(int x) throws ExceptionA, ExceptionB, ExceptionC {
if(x < 0) throw new ExceptionA("x < 0");
if(x == 0) throw new ExceptionB("x == 0");
if(x > 0) throw new ExceptionC("x > 0");
}
public static void main(String[] args) {
try {
// f(0);
f(1);
f(-1);
// will catch any Exception type:
} catch(Exception e) {
System.out.println("Caught Exception");
e.printStackTrace(System.out);
}
}
}
====================================================================Caught Exception
第十二章异常处理.ExceptionC: x > 0
at 第十二章异常处理.No9Ex.f(No9Ex.java:24)
at 第十二章异常处理.No9Ex.main(No9Ex.java:29)
12.6.1 栈轨迹
printStrackTrace()方法提供的信息可通过 getStackTree()方法来直接访问,这个方法返回一个由栈轨迹中的元素所构成的数组。其中每一个元素表示栈的一帧,元素0表示栈顶元素,调用序列中的最后一个方法调用,最后一个元素是调用序列中的第一个方法调用。
12.6.1 重新抛出异常
重抛异常会把异常抛给上一级环境中的异常处理程序,PrintStackTrace()方法显示的将原来异常抛出点的调用栈西悉尼,而不是重新抛出点的信息。
使用fillInStackTrace(),返回一个Throwable对象,当前调用栈信息填入原来那个异常对象而建立的,异常新发地,有关原来异常发生点地信息丢失,剩下地是与新的抛出点有关的信息
不必对异常对象地清理而担心,它们都是new在堆上创建地对象,垃圾回收期会自动把它们清理掉
12.6.3 异常链
想在捕获一个异常后抛出另一个异常,并且希望把原始地异常地信息保存下来,被称为异常链
所有的Throwable都可以接受一个cause对象作为参数,cause用来表示原始异常,这样传递给新异常。
三种基本异常类提供了带cause的参数构造器:
- Error java 虚拟机报告系统错误
- Exception
- RuntimeException
如果要把其他类型连接起来,应该使用initCause()方法
练习10
/* Create a class with two methods, f() and g(). In g(), throw an exception of
* a new type that you define. In f(), call g(), catch its exception and, in the
* catch clause, throw a different exception (of a second type that you define).
* Test your code in main().
*/
class GException extends Exception {
GException(String s) { super(s); }
}
class HException extends Exception {
HException(String s) { super(s); }
}
public class No10Ex {
static void f() {
try {
try {
g();
} catch(GException ge) {
System.out.println("Caught GException in f inner try");
ge.printStackTrace();
throw new HException("from f(), inner try");
}
} catch(HException he) {
System.out.println("Caught HException in f() outer try");
he.printStackTrace(System.out);
}
}
static void g() throws GException {
throw new GException("from g()");
}
public static void main(String[] args) {
f();
}
}
======================================================================
Caught GException in f inner try
Caught HException in f() outer try
第十二章异常处理.HException: from f(), inner try
at 第十二章异常处理.No10Ex.f(No10Ex.java:25)
at 第十二章异常处理.No10Ex.main(No10Ex.java:36)
第十二章异常处理.GException: from g()
at 第十二章异常处理.No10Ex.g(No10Ex.java:33)
at 第十二章异常处理.No10Ex.f(No10Ex.java:21)
at 第十二章异常处理.No10Ex.main(No10Ex.java:36)
练习11
// TIJ4 Chapter Exceptions, Exercise 11, page 468
/* Repeat the previous exercise, but inside the catch clause, wrap g()'s
* exception in a RuntimeException.
*/
public class No11Ex {
static void f() {
try {
g();
} catch(GException ge) {
System.out.println("Caught GException in f try");
ge.printStackTrace();
throw new RuntimeException(ge);
}
}
static void g() throws GException {
throw new GException("from g()");
}
public static void main(String[] args) {
f();
}
}
==============================================================
Caught GException in f try
第十二章异常处理.GException: from g()
at 第十二章异常处理.No11Ex.g(No11Ex.java:20)
at 第十二章异常处理.No11Ex.f(No11Ex.java:12)
at 第十二章异常处理.No11Ex.main(No11Ex.java:23)
Exception in thread "main" java.lang.RuntimeException: 第十二章异常处理.GException: from g()
at 第十二章异常处理.No11Ex.f(No11Ex.java:16)
at 第十二章异常处理.No11Ex.main(No11Ex.java:23)
Caused by: 第十二章异常处理.GException: from g()
at 第十二章异常处理.No11Ex.g(No11Ex.java:20)
at 第十二章异常处理.No11Ex.f(No11Ex.java:12)
... 1 more
12.7 Java标准异常
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。
两种类型:
- Error用来表示编译时和系统错误
- Exception 而可以被抛出的基本类型,Java类库,用户方法以及运行时故障都可能抛出Exception型异常。
12.7.1 特例:RuntimeException
Java的标准运行时会检查传递给方法的每个引用其是否为null,所以你不必对每个传递给方法的每个引用都检查其是否为null
运行时异常类型有很多,它们会自动被Java虚拟机抛出,所以不必再异常说明中把它们列出来。这些异常都是RuntimeException继承过来的,”不受检查异常“。这种异常属于错误,将被自动捕获,就不用你亲自动手了。
只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
- 无法预料的错误,你从控制范围之外传递进来的null引用
- 在代码进行中检查的错误。
异常被设计
- 处理一些烦人的运行时错误,代码控制能力之外的因素导致
- 发现某些编辑器无法检测道德编程错误
12.8 使用finally进行清理
无论try块中的异常是否抛出,他都能得到执行。这通常适用于内存回收之外的情况
12.8.1 finally用来做什么
对于没有垃圾回收和 析构函数(当对象不再被使用的时候调用的函数C++)的语言来说,finally非常重要。它是程序员保证:无论try块里发生了什么,内存总能得到释放。Java什么情况下才能用的finally呢?
当要把除内存资源之外的资源恢复到它们的初始状态时,就要用到finally语句
- 已经打开的文件或者网络连接
- 在屏幕上画图形
- 外部世界的某个开关
在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高的一层的异常处理程序之前,执行finally语句,当涉及break和continue语句的时候,finally语句也会得到执行。
练习13
/* Modify Exercise 9 by adding a finally clause. Verify that your
* finally clause is executed, even if a NullPointerException is thrown.
*/
class ExceptionA extends Exception {
ExceptionA(String msg) { super(msg); }
}
class ExceptionB extends Exception {
ExceptionB(String msg) { super(msg); }
}
class ExceptionC extends Exception {
ExceptionC(String msg) { super(msg); }
}
public class No13Ex {
// array element will be initialized to null:
private static Integer[] x = new Integer[1];
public static void f(int x)
throws ExceptionA, ExceptionB, ExceptionC {
if(x < 0) throw new ExceptionA("x < 0");
if(x == 0) throw new ExceptionB("x == 0");
if(x > 0) throw new ExceptionC("x > 0");
}
public static void main(String[] args) {
try {
// to throw NullPointerException:
f(x[0]);
f(0);
f(1);
f(-1);
// will catch any Exception type:
} catch(Exception e) {
System.out.println("Caught Exception");
e.printStackTrace(System.out);
} finally {
System.out.println("made it to finally");
}
}
}
=========================================================================
Caught Exception
第十二章异常处理.ExceptionB: x == 0
at 第十二章异常处理.No13Ex.f(No13Ex.java:26)
at 第十二章异常处理.No13Ex.main(No13Ex.java:33)
made it to finally
练习14
// Show that the OnOffSwitch.java can fail by throwing a
// RuntimeException inside the try block.
public class OnOffSwitch14 {
private static Switch sw = new Switch();
static Integer[] x = new Integer[1];
public static void f(int i)
throws OnOffException1, OnOffException2 {}
public static void main(String[] args) {
try {
sw.on();
// Code that can throw RuntimeException
// and leave Switch on:
f(x[0]);
sw.off();
} catch(OnOffException1 e) {
System.out.println("OnOffException1");
sw.off();
} catch(OnOffException2 e) {
System.out.println("OnOffException2");
sw.off();
}
}
}
练习15
// Show that WithFinally.java doesn't fail by throwing a
// RuntimeException inside the try block.
public class WithFinally15 {
private static Switch sw = new Switch();
// set up x[0] = null:
private static Integer[] x = new Integer[1];
public static void f(int i)
throws OnOffException1, OnOffException2 {}
public static void main(String[] args) {
try {
sw.on();
// Code to throw NullPointerException:
f(x[0]);
} catch(OnOffException1 e) {
System.out.println("OnOffException1");
} catch(OnOffException2 e) {
System.out.println("OnOffException2");
} finally {
sw.off();
}
}
}
12.8.2 在return中使用finally
因为finally子句总归是会执行的,所以在一个方法中,可以多个点返回,并且可以保证重要的清理工作仍旧会执行 。
18.3.3 缺憾:异常丢失
Java的异常实现也有瑕疵。程序出错的标志,
用某些特殊的final子句,就会发生这种情况
- 里层没有catch里层try语句直接final
- final子句return 即使抛出了异常,不会产生任何输出
练习18
// Add a second level of exception loss to LostMessage.java so that the
// HoHumException is itself replaced by a third exception.
class VeryImportantException extends Exception {
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
public String toString() {
return "A trivial exception";
}
}
class MeaninglessException extends Exception {
public String toString() {
return "A meaningless exception";
}
}
public class No18Ex {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void dispose() throws HoHumException {
throw new HoHumException();
}
void eliminate() throws MeaninglessException {
throw new MeaninglessException();
}
public static void main(String[] args) {
try {
No18Ex lm = new No18Ex();
try {
try {
lm.f();
lm.dispose();
} finally {
lm.eliminate();
}
} catch(Exception e) {
System.out.println(e);
}
} catch(Exception e) {
System.out.println(e);
}
}
}
==========================================================
A meaningless exception
练习19
public class LostMessageFound19 {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args) {
try {
LostMessageFound19 lmf = new LostMessageFound19();
try {
lmf.f();
} catch(Exception e) {
System.out.println(e);
} finally {
lmf.dispose();
}
} catch(Exception e) {
System.out.println(e);
}
}
}
A very important exception!
A trivial exception
12.9 异常的限制
当覆盖方法的时候,只能抛出在其基类方法的异常说明里列出的那些异常。
这意味着当基类使用的代码应用到其派生类对象的时候,一样能够工作(面向对象的基本概念):
-
方法声明将抛出异常,但实际上没有抛出,这种方式使你能强制用户去捕获可能在覆盖后的event()版本中增加的异常,所以它很合理。这对于抽象方法同样成立。
-
如果一个类继承了基类和实现了一个接口,那么接口不能向基类中现有的方法增加异常,或改变异常
-
如果接口里定义的方法不是来自基类,那么方法抛出什么样的异常也都没有问题。或者实现现有方法,但可以不抛出异常
-
对构造器没有限制,继承类的构造器可以抛出任何异常,而不必理会基类构造所抛出的异常,但是必须包含基类构造器异常
-
派生类构造器不能捕获基类构造器异常
-
通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证
-
可以向上转型为基类,编译器就会要求你捕获基类的异常,额能产生更强制的异常类代码
继承过程中,编译器会对异常进行强制要求,但异常说明本身并不属于方法类型的一部分,不基于异常说明来重载方法。一个出现在基类方法的异常说明的异常,不一定出现在派生类方法的异常说明里。在继承和覆盖的过程中,基类的方法必须出现在派生类里,”某个特定方法的异常说明接口不是变大而是变小的,与接口和类在继承时情形相反 “
练习20
/* MOdify StormyInning.java by adding an UmpireArgument exception type
* and methods that throw this exception. Test the modified hierarchy.
*/
// Overridden methods may throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions.
import 第十章内部类.No20;
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
class UmpireArgument extends BaseballException {}
class ThrownFromGame extends UmpireArgument {}
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, UmpireArgument;
public void questionableCall() throws UmpireArgument {}
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 No20StormyInning extends Inning implements Storm {
// OK to add new exceptions for constructors, but you
// must deal with the base constructor exceptions:
public No20StormyInning()
throws UmpireArgument, RainedOut, BaseballException {}
public No20StormyInning(String s)
throws Foul, BaseballException {}
// Regular methods must comform 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 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 class version does:
public void event() {}
// Overridden methods can throw inherited exceptions:
public void atBat() throws PopFoul, ThrownFromGame {
throw new ThrownFromGame();
}
public void questionableCall() throws UmpireArgument {
throw new UmpireArgument();
}
public static void main(String[] args) {
try {
No20StormyInning si = new No20StormyInning();
si.atBat();
si.questionableCall();
} catch(PopFoul e) {
System.out.println("Pop foul");
} catch(UmpireArgument e) {
System.out.println("Umpire argument (StormyInning20)");
// } catch(ThrownFromGame e) {
// System.out.println("Thrown from game");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
// Strike not thrown in derived version.
try {
// What happens if you upcast?
Inning i = new No20StormyInning();
i.atBat();
// You must catch the exceptions from
// the base-class version of the method:
} catch(Strike e) {
System.out.println("Strike");
} catch(Foul e) {
System.out.println("Foul");
} catch(ThrownFromGame e) {
System.out.println("Thrown from game (Inning)");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
}
}
========================================================================
Umpire argument (StormyInning20)
Thrown from game (Inning)
12.10 构造器
如果异常发生了,所有的东西能被正确的清理吗?大多数情况时非常安全的,但涉及构造器时,问题就出现了,构造器会把对象设置成安全的初始状态。编写构造器时要格外小心。如果构造器抛出异常,就不能很好的初始化,导致清理行为也可能不能正常工作。
处理那些具有可失败的构造器,且需要清理的对象。为了正确处理这种情况,对于每个构造器,都必须包含在其自己的try-finally语句中,并且每个对象构造必须都跟随一个try-fianlly语句块以确保清理。
练习21
// Demonstrate that a derived-class constructor cannot catch exceptions thrown
// by its base-class constructor.
class BaseException extends Exception {}
class Base {
Base() throws BaseException {
throw new BaseException();
}
}
class Derived extends Base {
// BaseException must be caught (no way) or
// declared to be thrown:
Derived() throws BaseException {
super();
// not this way, 'catch' without 'try' not allowed:
// catch(BaseException e) {}
// not this way either, because call to super
// must be first statement in constructor:
// try {
// super();
// } catch(BaseException e) {}
}
}
public class No21Derived {
public static void main(String[] args) {
try {
Derived d = new Derived();
} catch(BaseException e) {
System.out.println("BaseException caught in main()");
}
}
}
========================================================================
BaseException caught in main()
练习22
/* Create a class called FailingConstructor with a constructor that might fail
* partway through the construction process and throw an exception. In main(),
* write code that properly guards against this failure.
*/
public class No22Ex {
Integer[] ia = new Integer[2];
String s;
No22Ex(String s) throws Exception {
ia[0] = 0;
ia[1] = 1;
ia[2] = 2;
this.s = s;
}
public static void main(String[] args) {
try {
No22Ex fc = new No22Ex("hi");
} catch(Exception e) {
System.err.println("Caught Exception in main()");
e.printStackTrace(System.err);
} finally {
}
}
}
===================================================================
Caught Exception in main()
java.lang.ArrayIndexOutOfBoundsException: 2
at 第十二章异常处理.No22Ex.<init>(No22Ex.java:13)
at 第十二章异常处理.No22Ex.main(No22Ex.java:18)
练习23
/* Add a class with a dispose() method to the previous exercise. Modify
* FailingConstructor so that the constructor creates one of these disposable
* objects, after which the constructor might through an exception, after which
* it creates a second disposable member object. Write code to properly guard
* against failure, and in main() verify that all possible failure situations
* are covered.
*/
// This solution satisfies the conditions called for in FailingConstructor23
// constructor, which catches its own exceptions.
class Disposable {
private static int counter = 0;
private int id = counter++;
private boolean disposed;
Disposable() {
disposed = false;
}
void dispose() {
disposed = true;
}
String checkStatus() {
return (id + " " + (disposed ? "disposed" : "not disposed"));
}
}
public class No22FailingConstructor {
private Integer[] ia = new Integer[2];
private static Disposable d0;
private static Disposable d1;
No22FailingConstructor() throws Exception {
try {
d0 = new Disposable();
try {
ia[2] = 2; // causes exception thrown and
// caught in middle try loop
try {
d1 = new Disposable();
} catch(Exception e) {
System.out.println("Caught e in inner try loop");
e.printStackTrace(System.err);
System.out.println("Failed to create d1");
}
} catch(Exception e) {
System.out.println("Caught e in middle try loop");
e.printStackTrace(System.err);
System.out.println("Disposing d0");
d0.dispose(); // d0 would have been created
}
} catch(Exception e) {
System.out.println("Caught e in outer try loop");
e.printStackTrace(System.err);
System.out.println("Failed to create d0");
}
}
public static void main(String[] args) {
try {
// the constructor catches its own exceptions:
No22FailingConstructor fc = new No22FailingConstructor();
} catch(Exception e) {
System.err.println("Caught Exception in main()");
e.printStackTrace(System.err);
}
}
}
=========================================================
Caught e in middle try loop
Disposing d0
java.lang.ArrayIndexOutOfBoundsException: 2
at 第十二章异常处理.No22FailingConstructor.<init>(No22FailingConstructor.java:36)
at 第十二章异常处理.No22FailingConstructor.main(No22FailingConstructor.java:61)
练习24
/* Add a dipsose() method to the FailingConstructor class and write code to properly use
* this class.
*/
// Solution modeled from examples in text:
import java.io.*;
public class No24Ex {
private BufferedReader in;
public No24Ex(String fname) throws Exception {
try {
in = new BufferedReader(new FileReader(fname));
} catch(FileNotFoundException e) {
System.out.println("Could not find file " + fname);
throw e;
} catch(Exception e) {
try {
in.close();
} catch(IOException e2) {
System.out.println("in.close() failed");
}
throw e;
}
}
public String getLine() {
String s;
try {
s = in.readLine();
} catch(IOException e) {
throw new RuntimeException("readLine() failed");
}
return s;
}
public void dispose() {
try {
in.close();
System.out.println("dispose() successful");
} catch(IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
public static void main(String[] args) {
try {
No24Ex fc =
new No24Ex("No22Ex.java");
try {
String s;
int i = 1;
while((s = fc.getLine()) != null) {
// code to print to new file:
// println(i + " " + s);
// i++;
}
} catch(Exception e) {
System.out.println("Exception caught in main()");
e.printStackTrace(System.err);
} finally {
fc.dispose();
}
} catch(Exception e) {
System.out.println("FailingConstructor22b construction failed");
}
}
}
===================================================
Could not find file No22Ex.java
FailingConstructor22b construction failed
12.11 异常匹配
抛出异常的时候,异常处理系统会按照代码书写顺序找出 最近 的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找
派生类的对象也可以匹配其基类的处理程序
不能把基类的 catch 子句放在最外面,把派生类的异常放在里面,这样派生类异常永远得不到执行
/* Create a three-level hierarchy of exceptions. Now create a
* base-class A with a method that throws an exception at the base
* of your hierarchy. Inherit B from A and override the method so
* it throws an exception at level two of your hierarchy. Repeat by
* inheriting class C from B. In main(), create a C and upcast it
* to A, then call the method.
*/
class LevelOneException extends Exception {}
class LevelTwoException extends LevelOneException {}
class LevelThreeException extends LevelTwoException {}
class A {
void f() throws LevelOneException {
throw new LevelOneException();
}
}
class B extends A {
void f() throws LevelTwoException {
throw new LevelTwoException();
}
}
class C extends B {
void f() throws LevelThreeException {
throw new LevelThreeException();
}
}
public class No25Ex {
public static void main(String[] args) {
A a = new C();
try {
a.f();
} catch(LevelThreeException e3) {
System.out.println("Caught e3");
} catch(LevelTwoException e2) {
System.out.println("Caught e2");
} catch(LevelOneException e1) {
System.out.println("Caught e1");
}
}
}
=================================================================
Caught e3
12.12 其他可选方式
异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。
重要原则:只有在你知道如果处理的情况下才捕获异常
重要目标:错误处理的代码同错误发生的地点相分离
异常检查会强制你在可能还没准备好处理错误的时候被迫假设catch语句,导致吞噬则害错误(harmful is swallowed)Java是强静态语言, 编译时就做类型检查的语言。
研究 被检查的异常 及其并发症,采取什么方法来解决问题
Java是强类型语言(编译时就会做类型检查的语言),要对类型检查持怀疑态度。(所有模型都是错误的,但是都是有用的)
- 不在于编译器是否会强制程序员去处理错误,而是要有一致的,使用异常来报告错误的模型
- 不在于什么时候进行检查,而是一定要有类型检查,也就是说必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那到没关系
通过把异常传递给控制台,就不必在main()里写try catch子句了
把”被检查的异常“转换为”不坚持的异常“,可以把”被检查的异常“包装进RuntimeException里面。
把 被检查的异常 这种功能屏蔽
练习27
// Modify Exercise 3 to convert the exception to a Runtime Exception.
public class No27Ex {
private static int[] ia = new int[2];
public static void main(String[] args) {
try {
ia[2] = 3;
} catch(ArrayIndexOutOfBoundsException e) { // convert to RuntimeException:
throw new RuntimeException(e);
}
}
}
练习28
/* Modify Exercise 4 so that the custom exception class inherits from
* RuntimeException, and show that the compiler allows you to leave
* out the try block.
*/
class Exception28 extends RuntimeException {
private String msg;
Exception28(String msg) {
super(msg);
System.out.println("Exception28()");
this.msg = msg;
}
protected void showS() {
System.out.println("Message from Exception4: " + msg);
}
}
public class Ex28 {
public static void f() throws Exception28 {
System.out.println("f()");
throw new Exception28("Ouch from f()");
}
public static void main(String[] args) {
f();
}
}
==============================================================
f()
Exception28()
Exception in thread "main" 第十二章异常处理.Exception28: Ouch from f()
at 第十二章异常处理.No28Ex.f(No28Ex.java:23)
at 第十二章异常处理.No28Ex.main(No28Ex.java:26)
练习29
/* Modify all the exception types in StormyInning.java so that they extend
* RuntimeException, and show that no exception specifications or try blocks
* are necessary. Remove the '//!' comments and show how the methods can be
* compiled without specifications.
*/
class BaseballException extends RuntimeException {}
class Foul extends RuntimeException {}
class Strike extends RuntimeException {}
abstract class Inning {
public Inning() {}
public void event() {}
public abstract void atBat();
public void walk() {}
}
class StormException extends RuntimeException {}
class RainedOut extends RuntimeException {}
class PopFoul extends RuntimeException {}
interface Storm {
public void event();
public void rainHard();
}
public class StormyInning29 extends Inning implements Storm {
public StormyInning29() {}
public StormyInning29(String s) {}
public void walk() {}
public void event() {}
public void rainHard() {}
public void atBat() {}
public static void main(String[] args) {
StormyInning29 si = new StormyInning29();
si.atBat();
// What happens if you upcast?
Inning i = new StormyInning29();
i.atBat();
}
}
练习30
/* Modify Human.java so that the exceptions inherit from
* RuntimeException. Modify main() so that the technique
* in TurnOffChecking.java is used to handle the different
* types of exceptions.
*/
class Annoyance extends RuntimeException {}
class Sneeze extends Annoyance {}
class WrapCheckedExceptions {
void throwRuntimeException(int type) {
try {
switch(type) {
case(0):
throw new Annoyance();
case(1):
throw new Sneeze();
case(2):
throw new RuntimeException("Where am I?");
default: return;
}
} catch(Exception e) {
// Adapt to unchecked:
throw new RuntimeException(e);
}
}
}
public class No30Ex {
public static void main(String[] args) {
WrapCheckedExceptions wce =
new WrapCheckedExceptions();
for(int i = 0; i < 3; i++)
try {
if(i < 3)
wce.throwRuntimeException(i);
else
throw new RuntimeException();
} catch(RuntimeException re) {
try {
throw re.getCause();
} catch(Sneeze e) {
System.out.print("Sneeze: " + e);
} catch(Annoyance e) {
System.out.println("Annoyance: " + e);
} catch(Throwable e) {
System.out.println("Throwable: " + e);
}
}
}
}
=====================================================================
Annoyance: 第十二章异常处理.Annoyance
Sneeze: 第十二章异常处理.SneezeThrowable: java.lang.RuntimeException: Where am I?
12.13 异常使用指南
12.14 总结
如果不使用异常,那你只能完成很有限的工作。报告功能时异常的精髓所在,恢复可能不占10%
,恢复也只是展开异常栈。