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

【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)异常处理的目标:把错误处理的代码和错误发生的地点相分离;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值