您的位置:首页 > 编程语言 > Java开发

thinking-in-java(12)通过异常处理错误

2017-12-01 23:37 447 查看
【12.0】开场白
1)java的基本理念:结构不佳的代码不能运行;
2)改进的错误恢复机制:是提供代码健壮性的最强有力的方式;
3)java异常:
3.1)java采用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠沟通;
3.2)java异常的目的:简化大型,可靠程序的生成,确保你的应用中没有未处理的错误;
3.3)异常处理是java中唯一正式的错误报告机制:通过编译器强制执行;

【12.1】概念
1)异常的好处:能够降低错误处理代码的复杂度;

【12.2】基本异常
1)异常情形:是指阻止当前方法或作用域继续执行的问题;
2)异常处理程序:是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去;

【12.2.1】异常参数
1)标准异常类有2个构造器:一个是默认构造器;另一个是接受字符串作为参数的构造器;
/*异常构造器的荔枝 */
public class ExceptionTest {
public static void main(String[] args) {
int flag = 1;

if (flag == 1) {
throw new NullPointerException("t = null");
} else
throw new NullPointerException();
}
}
/*
Exception in thread "main" java.lang.NullPointerException: t = null
at chapter12.ExceptionTest.main(ExceptionTest.java:5)
*/

【代码解说】
1)把异常处理看做是一种返回机制: 因为可以用抛出异常的方式从当前作用域中退出;
2)Throwable是异常类型的基类:可以抛出任意类型的 Throwable对象;

【12.3】捕获异常
【12.3.1】try块
1)介绍:在try块中尝试各种方法调用;

【12.3.2】异常处理程序
1)catch块表示异常处理程序:在try块之后;
2)终止与恢复:
2.1)异常处理理论上有两种基本模型:终止模型 和 恢复模型;
2.2)程序员通常使用了 终止模型,而不怎么使用恢复模型;

【12.4】创建自定义异常
1)建立新异常类的最简单方法:让编译器为你产生默认构造器;这通过 继承 Exception 来实现;
/* 荔枝-自定义异常 */
class SimpleException extends Exception {} // 自定义异常

public class InheritingExceptions {
public void f() throws SimpleException {
System.out.println("Throw SimpleException from f()");
throw new SimpleException();
}

public static void main(String[] args) {
InheritingExceptions sed = new InheritingExceptions();
try {
sed.f(); // 1
} catch (SimpleException e) {
System.out.println("Caught it!"); // 2
}
System.out.println("continue."); //3, 恢复模型
}
}
/*
Throw SimpleException from f()
Caught it!
continue.
*/

2)把错误信息发送给 标准错误流 System.err 比发给 标准输出流 System.out 要好:因为System.out 也许被重定向,而System.err 不会随System.out 一起被重定向;
/* 荔枝-错误信息被定向到标准输出流 System.out 还是 标准错误输出流System.err */
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}
public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}

public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}

public static void main(String[] args) {
try {
f();
} catch (MyException e) {
// public final static PrintStream out = null;
e.printStackTrace(System.out); // 重定向到 标准输出
}
try {
g();
} catch (MyException e) {
// public final static PrintStream err = null;
e.printStackTrace(System.err); // 重定向到 标准错误输出
e.printStackTrace(); // (同上)重定向到 标准错误输出(默认)
}
}
}
/*
Throwing MyException from f()
chapter12.MyException
at chapter12.FullConstructors.f(FullConstructors.java:17)
at chapter12.FullConstructors.main(FullConstructors.java:27)
Throwing MyException from g()
chapter12.MyException: Originated in g()
at chapter12.FullConstructors.g(FullConstructors.java:22)
at chapter12.FullConstructors.main(FullConstructors.java:32)
*/

【代码解说】
1)printStackTrace()方法:打印 “从方法调用处到抛出异常处” 的方法调用序列栈;
2)printStackTrace() 默认输出流:标准错误输出流;

【12.4.1】异常与记录日志
【荔枝-异常与记录日志 】
/* 荔枝-异常与记录日志 */
class LoggingException extends Exception { // 自定义异常类型
private static Logger logger = Logger.getLogger("LoggingException");
public LoggingException() {
StringWriter trace = new StringWriter();
// step2, 6
printStackTrace(new PrintWriter(trace)); // 打印异常信息,即打印方法调用序列(从方法调用处到抛异常处)到 StringWriter 输出流;
// step3, 7
logger.severe(trace.toString()); // 严重日志:调用与日志级别相关联的方法。(日志级别为严重 severe)
}
}
public class LoggingExceptions {
public static void main(String[] args) throws InterruptedException {
try {
System.out.println("抛出第一个异常"); // step1
throw new LoggingException();
} catch (LoggingException e) {
System.err.println("Caught " + e); // step4
}
try {
Thread.sleep(1000);
System.out.println("抛出第二个异常"); // step5
throw new LoggingException();
} catch (LoggingException e) {
System.err.println("Caught " + e); // step8
}
}
}
/*
抛出第一个异常
十一月 27, 2017 11:49:38 上午 chapter12.LoggingException <init> // 日志
严重: chapter12.LoggingException // 日志
at chapter12.LoggingExceptions.main(LoggingExceptions.java:18) // 方法调用处到抛出异常处的方法调用栈

Caught chapter12.LoggingException // // 异常类的toString 方法
抛出第二个异常
十一月 27, 2017 11:49:39 上午 chapter12.LoggingException <init> // 日志
严重: chapter12.LoggingException // 日志
at chapter12.LoggingExceptions.main(LoggingExceptions.java:25) // 方法调用处到抛出异常处的方法调用栈

Caught chapter12.LoggingException // 异常类的toString 方法
*/

1)通常情况:需要捕获和记录其他人编写的异常,这就必须在异常处理程序中生成日志;
/* 荔枝-在异常处理程序中生成日志 */
public class LoggingExceptions2 {
private static Logger logger = Logger.getLogger("LoggingExceptions2");

static void logException(Exception e) {
StringWriter trace = new StringWriter();
logger.severe(trace.toString());
}
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (NullPointerException e) {
// 需要捕获和记录其他人编写的异常,这就必须在异常处理程序中生成日志;
logException(e);
}
}
}
/*
十一月 28, 2016 2:09:22 下午 chapter12.LoggingExceptions2 logException
严重:
*/

2)进一步自定义异常:如加入额外的构造器和成员;
// 自定义新异常,添加了字段x 以及设定x值的 构造器和 读取数据的方法.
class MyException2 extends Exception {
private int x;

public MyException2() {}
public MyException2(String msg) {  super(msg); }
public MyException2(String msg, int x) { //额外的构造器 和 成员
super(msg);
this.x = x;
}
public int val() { return x; }
// 还覆盖了 Throwable.getMessage() 方法, 以产生更详细的信息.
@Override
public String getMessage() {
return "Detail Message: " + x + " " + super.getMessage();
}
}
public class ExtraFeatures {
public static void f() throws MyException2 {
print("Throwing MyException2 from f()");
throw new MyException2();
}
public static void g() throws MyException2 {
print("Throwing MyException2 from g()");
throw new MyException2("Originated in g()"); //额外的构造器
}
public static void h() throws MyException2 {
print("Throwing MyException2 from h()");
throw new MyException2("Originated in h()", 47); //额外的构造器
}
public static void main(String[] args) {
try {
f();
} catch (MyException2 e) {
e.printStackTrace(System.out);
}
try {
g();
} catch (MyException2 e) {
e.printStackTrace(System.out);
}
try {
h();
} catch (MyException2 e) {
e.printStackTrace(System.out);
System.out.println("e.val() = " + e.val());
}
}
}
/*
Throwing MyException2 from f()
chapter12.MyException2: Detail Message: 0 null
at chapter12.ExtraFeatures.f(ExtraFeatures.java:20)
at chapter12.ExtraFeatures.main(ExtraFeatures.java:32)
Throwing MyException2 from g()
chapter12.MyException2: Detail Message: 0 Originated in g()
at chapter12.ExtraFeatures.g(ExtraFeatures.java:24)
at chapter12.ExtraFeatures.main(ExtraFeatures.java:37)
Throwing MyException2 from h()
chapter12.MyException2: Detail Message: 47 Originated in h()
at chapter12.ExtraFeatures.h(ExtraFeatures.java:28)
at chapter12.ExtraFeatures.main(ExtraFeatures.java:42)
e.val() = 47
*/

【代码解说】
1)自定义新异常,添加了字段x 以及设定x值的 构造器和 读取数据的方法;
2)覆盖了 Throwable.getMessage()方法,以产生更详细的信息;(对于异常类来说,getMessage()方法类似于 toString()方法);

【12.5】异常说明
1)异常说明:java鼓励程序员把方法可能抛出的异常告知调用该方法的程序员,这是优雅的做法;以异常说明的形式进行告知;
2)异常说明:属于方法声明,在形式参数列表之后;
3)异常说明:使用了 throws 关键字,后面跟潜在潜在异常类型列表,方法定义如下:
void f() throws Exception1, Exception2, Exception3, ... {

4)如果抛出异常而没有进行处理:编译器提醒,要么处理这个异常,要么在异常说明中表明该方法将产生异常;(这是在编译的时候作出的提醒,所以这种异常称为编译时异常)
5)【编码技巧】作弊:声明抛出异常但是实际却没有抛出。好处是,为异常先占个位置,以后抛出这种异常就不用修改已有代码。在定义抽象基类和接口时这种能力很重要的,
这样派生类或接口实现类就能够抛出这些预先声明的异常;(不能再干货)
6)被检查的异常(编译时异常):这种在编译时被强制检查的异常;

【12.6】捕获所有异常
1)通过捕获异常类型的基类 Exception 捕获所有异常;通常把 捕获Exception 的 catch 子句放在处理程序列表(catch块列表)末尾,避免它抢在其他处理程序前被捕获了;
2)public class Exception extends Throwable,Exception常用的方法列表:
getMessage():获取详细信息;
getLocalizedMessage():获取本地语言表示的详细信息;
toString()

3)打印Throwable 的调用栈轨迹(抛出异常处到方法调用处):
printStackTrace(); 输出到标准错误输出流;
printStackTrace(PrintStream);
printStackTrace(java.io.PrintWriter);

4)Throwable fillStackTrace():在Throwable对象内部记录栈帧的当前状态。这在程序重新抛出异常或错误非常有用;
5)Throwable继承自Object的其他方法:
getClass():返回对象类型的对象;
getClass().getName():返回对象信息名称;
getClass().getSimpleName():

【荔枝-如何使用Exception的方法】
// Exception 基类 Throwable 的方法列表.
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch (Exception e) {
print("Caught Exception");
print("getMessage(): " + e.getMessage()); // getMessage():My Exception
print("getLocalizedMessage(): " + e.getLocalizedMessage()); // getLocalizedMessage():My Exception
print("toString(): " + e); // toString():java.lang.Exception: My Exception
print("printStackTrace(): ");
e.printStackTrace(System.out); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取;
}
}
}
/*
Caught Exception
getMessage():My Exception
getLocalizedMessage():My Exception
toString():java.lang.Exception: My Exception
printStackTrace():
java.lang.Exception: My Exception
at chapter12.ExceptionMethods.main(ExceptionMethods.java:8)
*/

【代码解说】每个方法都比前一个方法打印出更多的信息,因为每一个都是前一个的超集;

【12.6.1】栈轨迹
1)栈轨迹:通过 printStackTrace()方法打印,打印的信息通过 getStackTrace() 来直接获取;
2)getStackTrace():该方法返回一个由栈轨迹中的元素所构成的数组 ;
// 荔枝-获取调用栈轨迹数组-getStackTrace()
public class WhoCalled {
static void f() {
try {
throw new Exception();
} catch (Exception e) {
// getStackTrace() 返回 栈轨迹的元素组成的数组
for (StackTraceElement ste : e.getStackTrace())
System.out.println(ste.getMethodName()); // 异常抛出地点的方法调用.
}
}
static void g() {
f();
}
static void h() {
g();
}
public static void main(String[] args) {
f();
System.out.println("--------------------------------");
g(); // g() -> f()
System.out.println("--------------------------------");
h(); // h() -> g() -> f()
}
}
/*
f
main
-------------------------------- 调用栈轨迹(先进后出): f() -> g() -> main, main() 最先调用, f() 最后调用
f
g
main
--------------------------------
f
g
h
main
*/

【12.6.2】重新抛出异常:分为重新抛出同一种类型的异常 还是 抛出另外一种类型的异常
1)重新抛出异常语法:
catch(Exception e) {
throw e;
}

2)重新抛出异常:会把异常抛给上一级环境的异常处理程序, 同一个try块的后续catch子句被会略;
3)重新抛出异常-fillInStackTrace():printStackTrace() 打印的是原来异常的调用栈信息,而不是新的异常抛出处的调用栈信息;fillInStackTrace() 返回一个Throwable对象,会把当前调用栈填入原来异常对象;

【荔枝-重新抛出异常】
// fillInStackTrace()  那一行成了异常的新发生地.
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); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取;
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
// fillInStackTrace()  那一行成了异常的新发生地.
// 调用 fillInStackTrace()方法后,轨迹栈 是 main()->h(),而不是main() ->h() -> f()
throw (Exception) e.fillInStackTrace();
}
}
public static void main(String[] args) {
try {
g(); // g() -> f()
} catch (Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out); // 打印抛出异常栈轨迹元素, 打印的信息通过 getStackTrace() 来直接获取;
}
System.out.println("\n\n main 方法中的 第二个 异常抛出, fillInStackTrace() 测试");
try {
h(); // h() -> f()
} catch (Exception e) {
System.out.println("main: printStackTrace()2");
e.printStackTrace(System.out);
}
}
}
/*
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at chapter12.Rethrowing.f(Rethrowing.java:6)
at chapter12.Rethrowing.g(Rethrowing.java:10)
at chapter12.Rethrowing.main(Rethrowing.java:28)
main: printStackTrace()
java.lang.Exception: thrown from f()
at chapter12.Rethrowing.f(Rethrowing.java:6)
at chapter12.Rethrowing.g(Rethrowing.java:10)
at chapter12.Rethrowing.main(Rethrowing.java:28)

main 方法中的 第二个 异常抛出
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at chapter12.Rethrowing.f(Rethrowing.java:6)
at chapter12.Rethrowing.h(Rethrowing.java:19)
at chapter12.Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
java.lang.Exception: thrown from f()
at chapter12.Rethrowing.h(Rethrowing.java:23)
at chapter12.Rethrowing.main(Rethrowing.java:35)
*/

【荔枝-重新抛出一种新异常】
// 荔枝-重新抛出一个新异常(捕获 OneException异常后, 抛出 TwoException 异常.)
class OneException extends Exception {
public OneException(String s) {
super(s);
}
}
class TwoException extends Exception {
public TwoException(String s) {
super(s);
}
}
public class RethrowNew {
public static void f() throws OneException {
System.out.println("originating the exception in f()");
throw new OneException("thrown from f()");
}
public static void main(String[] args) {
try {
try {
f();
} catch (OneException e) {
System.out.println("Caught in inner try, e.printStackTrace()");
e.printStackTrace(System.out);
// 在捕获异常后 抛出另外一种异常
// 捕获 OneException异常后, 抛出 TwoException 异常.
throw new TwoException("from inner try");
}
} catch (TwoException e) {
System.out.println("Caught in outer try, e.printStackTrace()");
e.printStackTrace(System.out); // 最外层的try-catch捕获的异常的调用栈信息 没有 f()方法调用
}
}
}
/*
originating the exception in f()
Caught in inner try, e.printStackTrace()
chapter12.OneException: thrown from f()
at chapter12.RethrowNew.f(RethrowNew.java:16)
at chapter12.RethrowNew.main(RethrowNew.java:21)
Caught in outer try, e.printStackTrace()
chapter12.TwoException: from inner try
at chapter12.RethrowNew.main(RethrowNew.java:25)
*/

【代码解说】 最外层的catch子句打印的调用栈信息只包含 main() 方法,不包含 f()方法的调用栈信息;

【12.6.3】异常链
1)异常链:常常需要在捕获第一个异常后抛出第二个异常,但想保留第一个异常的信息, 这被称为异常链;
2)三种基本的异常构造器提供了 cause 参数, 该参数吧原始异常(第一个异常)传递给第二个异常(新异常);
分别是Error,Exception 和 RuntimeException;
3)使用 ininCause() 方法可以把其他异常链连起来,
【荔枝-使用 ininCause() 方法可以把其他异常链连起来】
// 荔枝-通过initCause() 方法把 NullPointerException 插入到 DynamicFieldsException异常链中.
class DynamicFieldsException extends Exception {}

public class DynamicFields {
private Object[][] fields;
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for (int i = 0; i < initialSize; i++)
fields[i] = new Object[] { null, null };
}
public String toString() {
StringBuilder result = new StringBuilder();
for (Object[] obj : fields) {
result.append(obj[0]);
result.append(": ");
result.append(obj[1]);
result.append("\n");
}
return result.toString();
}

private int hasField(String id) {
for (int i = 0; i < fields.length; i++)
if (id.equals(fields[i][0]))
return i;
return -1;
}

private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if (fieldNum == -1)
throw new NoSuchFieldException();
return fieldNum;
}

private int makeField(String id) {
for (int i = 0; i < fields.length; i++)
if (fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// No empty fields. Add one:
Object[][] tmp = new Object[fields.length + 1][2];
for (int i = 0; i < fields.length; i++)
tmp[i] = fields[i];
for (int i = fields.length; i < tmp.length; i++)
tmp[i] = new Object[] { null, null };
fields = tmp;
// Recursive call with expanded fields:
return makeField(id);
}

public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
// 其他方法都可以忽略不看,看 这个方法setField() 即可。
public Object setField(String id, Object value)
throws DynamicFieldsException {
if (value == null) {
// Most exceptions don't have a "cause" constructor.
// In these cases you must use initCause(),
// available in all Throwable subclasses.
DynamicFieldsException dfe = new DynamicFieldsException();
// initCause() 方法把 NullPointerException 插入到 DynamicFieldsException异常链中.
dfe.initCause(new NullPointerException());
throw dfe; // 如果value=null,抛出异常
}
int fieldNumber = hasField(id);
if (fieldNumber == -1)
fieldNumber = makeField(id);
Object result = null;
try {
result = getField(id); // Get old value
} catch (NoSuchFieldException e) {
// Use constructor that takes "cause":
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}

public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
print("df = { " + df + " }");
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
print("df = { " + df + " }");

df.setField("d", "A new value for d");
df.setField("number3", 11);
print("df = { " + df + " }");

print("df.getField(\"d\") = " + df.getField("d"));

Object field = df.setField("d", null); // 把value设置为null,故意让setField()方法抛出异常
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
/*
Object field = df.setField("d", null); // 仅仅打印这句代码抛出的异常信息, 该代码把value设置为null,故意让setField()方法抛出异常
chapter12.DynamicFieldsException
at chapter12.DynamicFields.setField(DynamicFields.java:66)
at chapter12.DynamicFields.main(DynamicFields.java:100)
Caused by: java.lang.NullPointerException // 通过initCause() 方法把 NullPointerException 插入到 DynamicFieldsException异常链中.
at chapter12.DynamicFields.setField(DynamicFields.java:68)
... 1 more
*/

【12.7】Java标准异常
1)异常基类:Throwable;有两个子类,包括 Error 和 Exception;
1.1)Error:表示编译时和系统错误;(程序员不用关心)
1.2)Exception:用于方法和运行时可能抛出的异常类型;(关心);
【荔枝-Throwable, Exception, RuntimeException, Error, 源码】
public class Throwable implements Serializable {
public class Error extends Throwable {
public class Exception extends Throwable {
public class RuntimeException extends Exception {

【12.7.1】特例: RuntimeException-运行时异常
1)属于运行时异常的类型有很多:自动被jvm抛出,无需程序员抛出(但还是可以在代码中抛出 RuntimeException异常);因为运行时异常太多了,如果都去捕获的话,代码显得混乱;
2)不受检查的异常:运行时异常RuntimeException 也被称为 不受检查的异常;这种异常属于错误,被自动捕获;

【荔枝-显式抛出运行时异常】
// 荔枝-显式抛出运行时异常
public class NeverCaught {
static void f() {
throw new RuntimeException("From f()"); // 3-抛出异常
}
static void g() {
f(); // 2
}
public static void main(String[] args) {
g(); // 1
}
}
/*
Exception in thread "main" java.lang.RuntimeException: From f()
at chapter12.NeverCaught.f(NeverCaught.java:6)
at chapter12.NeverCaught.g(NeverCaught.java:9)
at chapter12.NeverCaught.main(NeverCaught.java:12)
*/

【注意】只能在代码中忽略 RuntimeException 及其子类的异常, 其他类型异常的处理都是由编译器强制实施的。
RuntimeException代表的是编程错误;
无法预料的错误;
应该在代码中进行检查的错误;

【12.8】使用 finally 进行清理
1)finally块: 无论try块中的异常是否抛出,finally 块的内容始终执行;
【荔枝-finally块】
// finally 的经典荔枝
class ThreeException extends Exception {}
public class FinallyWorks {
static int count = 0;
public static void main(String[] args) {
// 循环了两次,第一次抛出异常,第二次没有抛出异常,并在finally块中结束
while (true) {
try {
if (count++ == 0)
throw new ThreeException();
System.out.println("No exception"); // 3

} catch (ThreeException e) {
System.out.println("ThreeException"); // 1
} finally {
System.out.println("In finally clause"); // 2, 4
if (count == 2)
break;
}
}
}
}
/*
ThreeException
In finally clause
No exception
In finally clause
*/

【编程技巧】当java中的异常不允许程序回到异常抛出处,应该如何应对?把try块放在 循环里。(参考ThreeException.java 荔枝)

【12.8.1】finally 用来做什么?
1)finally块:保证 无论try块发生了什么,内存总能得到释放;
2)finally的用处:当把除内存之外的资源恢复到初始状态时,需要用到 finally块;

【荔枝- 内部finally 先于外部catch() 子句执行】
// 荔枝- 内部finally 先于外部catch() 子句执行。
// 无论try块中是否抛出异常, finally语句块被执行.
public class AlwaysFinally {
public static void main(String[] args) {
print("Entering first try block, 1"); // 1
try {
print("Entering second try block, 2"); // 2
try {
throw new FourException();
// java异常不允许我们回到 异常抛出地点,
// 故, 内部finally 先于外部catch() 子句执行.
} finally { // 内部finally
print("finally in 2nd try block, 3"); // 3
}
} catch (FourException e) { // 外部catch
System.out.println("Caught FourException in 1st try block, 4"); // 4
} finally {
System.out.println("finally in 1st try block, 5"); // 5
}
}
}
/*
Entering first try block, 1
Entering second try block, 2
finally in 2nd try block, 3
Caught FourException in 1st try block, 4
finally in 1st try block, 5
*/

【12.8.2】在return中使用finally:
1)return 返回前会执行finally子句或finally块中的内容;
// 荔枝-在return使用finally子句
public class MultipleReturns {
public static void f(int i) {
print("\n=== Initialization that requires cleanup, point = " + i);
try {
print("Point 1");
if (i == 1)
return;
print("Point 2");
if (i == 2)
return;
print("Point 3");
if (i == 3)
return;
print("End");
return;
// finally 子句总是会执行.
} finally {
print("Performing cleanup in finally clause.");
}
}
public static void main(String[] args) {
for (int i = 1; i <= 4; i++)
f(i);
}
}
/*
=== Initialization that requires cleanup, point = 1
Point 1
Performing cleanup in finally clause.

=== Initialization that requires cleanup, point = 2
Point 1
Point 2
Performing cleanup in finally clause.

=== Initialization that requires cleanup, point = 3
Point 1
Point 2
Point 3
Performing cleanup in finally clause.

=== Initialization that requires cleanup, point = 4
Point 1
Point 2
Point 3
End
Performing cleanup in finally clause.
*/


【12.8.3】缺憾:异常丢失

【荔枝】异常丢失的荔枝
// 异常丢失的荔枝
class VeryImportantException extends Exception {
public String toString() {
return "A very important exception from VeryImportantException!";
}
}
class HoHumException extends Exception {
public String toString() {
return "A trivial exception from HoHumException!";
}
}
public class LostMessage {
void throwVeryImportantException() throws VeryImportantException {
throw new VeryImportantException();
}
void throwHoHumException() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args) {
try {
LostMessage lm = new LostMessage();
try {
lm.throwVeryImportantException();  // 抛出  VeryImportantException 异常
// 先执行内部finally子句
} finally {
lm.throwHoHumException(); // 抛出  HoHumException 异常
}
// 后执行外部 catch 子句
} catch (Exception e) {
System.out.println(e);
}
}
}
/*
A trivial exception from HoHumException!  结果抛出了 HoHumException 异常, VeryImportantException 异常被覆盖了。
*/

【代码解说】VeryImportantException 异常被 finally 子句里的 HoHumException 所取代, 抛出的VeryImportantException异常丢失了;

【荔枝】异常丢失的荔枝2
// 异常丢失荔枝2
// 从finally 子句中返回,如果运行这个程序,即使抛出了异常,也不会产生任何输出;
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new RuntimeException();
} finally {
return; // 从finally 子句中返回
}
}
}
/*
不会产生任何输出
*/

【编码技巧】使用finally 要防止出现 异常丢失的情况;

【12.9】异常限制
1)当子类覆盖父类方法时:只能抛出基类方法的异常说明里列出的异常;

【编码技巧】异常限制对构造器不起作用,派生类构造器不能捕获基类构造器抛出的异常;
// BaseballException异常父类
class BaseballException extends Exception {} // BaseballException-棒球
class Foul extends BaseballException {} // Foul-犯规
class Strike extends BaseballException {} // Strike-击打

// Inning-(棒球)一局
abstract class Inning {
public Inning() throws BaseballException, NullPointerException {} // 构造器抛异常
public void event() throws BaseballException {} // event-事件
public abstract void atBat() throws Strike, Foul; // atBat-在球棒上
public void walk() {} // walk-行走
}
class StormException extends Exception {} // StormException-暴风雨异常
class RainedOut extends StormException {} // RainedOut- 因雨取消
class PopFoul extends Foul {} // PopFoul-流行犯规

interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm { // StormyInning-暴风雨中的一局棒球赛
public StormyInning() throws RainedOut, BaseballException {} // 构造器抛出异常,派生类构造器抛出的异常不受父类构造器抛出异常的限制
public StormyInning(String s) throws Foul, BaseballException {} // 构造器抛出异常
public void rainHard() throws RainedOut {} // rainHard-下大雨
public void event() {}
public void atBat() throws PopFoul {}

public static void main(String[] args) {
try {
StormyInning si = new StormyInning(); // 新建子类对象
si.atBat();
} catch (PopFoul e) {
System.out.println("Pop foul");
} catch (RainedOut e) {
System.out.println("Rained out");
} catch (BaseballException e) {
System.out.println("Generic baseball exception");
} catch(Exception e) {
System.out.println("Generic Exception");
}
try {
Inning i = new StormyInning(); // 父类指针指向子类对象
i.atBat();
} catch (Strike e) {
System.out.println("Strike");
} catch (Foul e) {
System.out.println("Foul");
} catch (RainedOut e) {
System.out.println("Rained out");
} catch (BaseballException e) {
System.out.println("Generic baseball exception");
}
}
} // 打印结果 为空

【代码解说】 Inning 是父类, StormyInning 是子类,而子类 StormyInning 抛出的异常不受父类 Inning 抛出异常的限制;如下:
public Inning() throws BaseballException, NullPointerException {} // 父类构造器抛异常;
public class StormyInning extends Inning implements Storm {
public StormyInning() throws RainedOut, BaseballException {} // 派生类构造器抛出的异常不受父类构造器抛出异常的限制;
public StormyInning(String s) throws Foul, BaseballException {} // 派生类构造器抛出的异常不受父类构造器抛出异常的限制;
// ......
}

【12.10】构造器
1)问题: 如果异常发生,所有东西都能够被清理吗? 当涉及到构造器时, 问题就出现了,即便finally 也不能完全解决。
2)如果在构造器内抛出异常,清理行为就不能正常工作了。所以 编写构造器时要格外小心;
3)如果构造器(如创建文件输入流)在其执行过程中半途而废(文件路径找不到,输入流创建失败),也许该对象还没有被成功创建,而这些部分在 finally 子句中却要被清理;(这容易触发空指针异常)

【荔枝-finally块 关闭了没有打开的文件输入流,抛出空指针异常的处理方法】
// 荔枝-如何处理finally块中抛出异常的情况
public class InputFile {
private BufferedReader in;

public InputFile(String fname) throws Exception {
try {
in = new BufferedReader(new FileReader(fname));
} catch (FileNotFoundException e) {
System.out.println("Could not open " + fname);
throw e;
} catch (Exception e) {
try {
in.close(); // close()方法也可能 抛出异常
} catch (IOException e2) {
System.out.println("in.close() unsuccessful");
}
throw e; // Rethrow
} finally {
// Don't close it here!!!
}
}
public String getLine() {
String s;
try {
s = in.readLine();
} catch (IOException e) {
throw new RuntimeException("readLine() failed");
}
return s;
}
// dispose-处理处置安排:关闭文件输入流
public void dispose() {
try {
in.close();
System.out.println("dispose() successful");
} catch (IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
}
// 在构造阶段可能抛出异常, 并且要求清理的类,使用到了 嵌套try 子句
public class Cleanup {
static String path = System.getProperty("user.dir")
+ File.separator  + "src" + File.separator + "chapter12" + File.separator;
// 用二层 try-catch
public static void main1(String[] args) {
try { // 外层try-catch
InputFile in = null;
try { // 双层 try语句块(嵌套try子句)
in = new InputFile(path + "erroPath" + "Cleanup.java"); // 错误路径
String s;
int i = 1;
while ((s = in.getLine()) != null);
} catch (Exception e) {
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
} finally {
in.dispose(); // 关闭文件输入流
}
} catch (Exception e) {
System.out.println("InputFile construction failed");
}
System.out.println("continue;"); // 即便抛出异常,还是继续执行;
}
/* 第一个main() 打印结果
Could not open D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.java
Caught Exception in main
java.io.FileNotFoundException: D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.java (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at java.io.FileReader.<init>(FileReader.java:58)
at chapter12.InputFile.<init>(InputFile.java:11)
at chapter12.Cleanup.main(Cleanup.java:14)
InputFile construction failed
continue;
*/
// 用一层 try-catch
public static void main(String[] args) {
InputFile in = null;

try { // 双层 try语句块(嵌套try子句)
in = new InputFile(path + "erroPath" + "Cleanup.java"); // 错误路径
String s;
int i = 1;
while ((s = in.getLine()) != null);
} catch (Exception e) {
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
} finally {
// 关闭文件输入流(如果文件路径错误或其他错误导致文件输入流没有打开的话,in为null,会抛出空指针异常)
in.dispose();
}
System.out.println("continue;"); // 如果抛出异常,程序执行终止,这句无法执行;
}
}
/* 第二个main() 打印结果
Could not open D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.java
Caught Exception in main
java.io.FileNotFoundException: D:\classical_books\thinking-in-java\AThinkingInJava\src\chapter12\erroPathCleanup.java (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at java.io.FileReader.<init>(FileReader.java:58)
at chapter12.InputFile.<init>(InputFile.java:11)
at chapter12.Cleanup.main(Cleanup.java:48)
Exception in thread "main" java.lang.NullPointerException
at chapter12.Cleanup.main(Cleanup.java:57)
*/

【代码解说】
解说1)如果FileReader 构造器失败,抛出 FileNotFoundException 异常。对于这种异常,不需要关闭文件,因为这个文件还没有打开。

然而,任何其他捕获异常的catch 子句必须关闭文件,因为捕获异常时,文件已经打开了。所以 这里就矛盾了。
解说2)所以finally块中的 in.dispose() 可能抛出异常;所以还需要再封装一层 try-catch (双层try-catch);
解说3)getLine()方法将异常转换为 RuntimeException, 表示这是一个编程错误;
解说4)最安全的使用方式:使用嵌套的try子句,就如 上面的 Cleanup.java 荔枝;

【编码技巧】在创建需要清理的对象后,立即进入一个 try-finally 语句块:

【荔枝-在创建需要清理的对象后,立即进入一个 try-finally 语句块】
class NeedsCleanup {
private static long counter = 1;
private final long id = counter++;
public void dispose() { // 清理内存 或 关闭输入李 或 其他清理操作
System.out.println("NeedsCleanup " + id + " disposed");
}
}
class ConstructionException extends Exception {}
class NeedsCleanup2 extends NeedsCleanup {
public NeedsCleanup2() throws ConstructionException {} // 构造器抛出异常
}

// 在创建需要清理的对象后,立即进入一个 try-finally 语句块.
public class CleanupIdiom {
public static void main(String[] args) {
// Section 1:
NeedsCleanup nc1 = new NeedsCleanup();
try {
// ...
} finally {
nc1.dispose(); // // 清理内存 或 关闭输入李 或 其他清理操作
}
// Section 2:
// If construction cannot fail you can group objects:
NeedsCleanup nc2 = new NeedsCleanup();
NeedsCleanup nc3 = new NeedsCleanup();
// 在创建需要清理的对象后,立即进入一个 try-finally 语句块.
try {
// ...
} finally {
nc3.dispose(); // 清理内存 或 关闭输入李 或 其他清理操作
nc2.dispose(); // 清理内存 或 关闭输入李 或 其他清理操作
}
// Section 3: 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。(处理方法是 3层try-catch块 )
// If construction can fail you must guard each one:
try {
NeedsCleanup2 nc4 = new NeedsCleanup2();
try {
NeedsCleanup2 nc5 = new NeedsCleanup2();
// 在创建需要清理的对象后,立即进入一个 try-finally 语句块.
try {
// ...
} finally {
nc5.dispose(); // 清理内存 或 关闭输入李 或 其他清理操作
}
} catch (ConstructionException e) { // nc5 constructor
System.out.println(e);
} finally {
nc4.dispose();
}
} catch (ConstructionException e) { // nc4 constructor
System.out.println(e);
}
}
}
/*
NeedsCleanup 1 disposed
NeedsCleanup 3 disposed
NeedsCleanup 2 disposed
NeedsCleanup 5 disposed
NeedsCleanup 4 disposed
*/

【代码解说】
1)Section 3 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。
2)处理方法是 3层try-catch块:对于每一个构造,都必须包含在 try-finally 块中,并且每一个对象构造都必须跟随一个 try-finally 块以确保清理;

【12.11】异常匹配
1)异常处理系统找到代码书写顺序最近的异常进行处理,且不再继续查找;

【荔枝-异常匹配】
// 异常匹配的荔枝
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

public class Human {
public static void main(String[] args) {
// Catch the exact type:
try {
throw new Sneeze(); // 抛出子类异常
} catch (Sneeze s) { // 捕获子类异常
System.out.println("Caught Sneeze");
} catch (Annoyance a) { // 捕获基类异常
System.out.println("Caught Annoyance 1");
}
// Catch the base type:
try {
throw new Sneeze(); // 抛出子类异常
} catch (Annoyance a) { //// 捕获基类异常
System.out.println("Caught Annoyance 2"); // 这里捕获异常
}
}
}
/*
Caught Sneeze
Caught Annoyance 2
*/

【代码解说】 catch (Annoyance a) 会捕获Annoyance 及其子类的异常;

2)如果把捕获基类异常的catch子句放在最前面,则会把后面子类的异常捕获子句 catch 子句 屏蔽掉,如:
try{
throw new Sneeze(); // 抛出子类异常
} catch (Annoyance a) { // // 捕获基类异常
System.out.println("Caught Annoyance 1"); // 这里捕获异常
} catch (Sneeze s) { //  捕获子类异常
System.out.println("Caught Sneeze"); // 异常捕获子句被屏蔽
}

3)编译器报错:编译器会发现 catch (Sneeze s) 永远不会执行,编译器报错;

【12.12】其他可选方式
1)异常处理的目标:把错误处理的代码和错误发生的地点相分离;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: