第十二章 通过异常处理错误
12.1 概念
12.2 基本异常
异常情形(exceptional condition):阻止当前方法或作用域继续执行的问题。
普通问题:在当前环境下能得到足够的信息,总能处理的问题。
当程序遇到异常情形而不知如何处理时,就需要抛出异常。在抛出异常时,首先会在堆中创建异常对象,之后终止当前的执行路径,并从当前环境中弹出异常对象的引用。之后交由异常处理程序进行处理。
12.2.1 异常参数
标准异常类都有两个构造器,一个是默认构造器,另外一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。
抛出异常使用throw关键字。
Throwable是所有异常类型的根类。
12.3 捕获异常
监控区域(guarded region):一段可能产生异常的代码。其后跟着处理这些异常的代码。
12.3.1 try块
try {
// Code that might generate exceptions
}
12.3.2 异常处理程序
异常处理程序紧跟在try块后,使用关键字catch来捕获相应的异常:
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type1
} catch(Type3 id3) {
// Handle exceptions of Type1
}
异常处理理论的两种模型:终止模型和恢复模型
终止模型:在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
恢复模型:异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。在Java中,可以在遇到错误时,调用相应的方法修正错误,或者把try块放入while循环中,不断进入try块,直到错误被修正。
一般使用的都是终止模型,因为恢复模型会导致耦合,它需要了解抛出异常的地点,不利于代码的编写和维护。
12.4 创建自定义异常
自定义异常类必须从继承已有的异常类。
12.4.1 异常与记录日志
记录自定义异常信息
package com.mzm.chapter12;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
/**
* 使用Logger对象记录自定义异常信息
*
*/
public class LoggingExceptions {
public static void main(String[] args){
try{
throw new LoggingException();
}catch (Exception e){
System.err.println("Caught " + e);
}
try{
throw new LoggingException();
}catch (Exception e){
System.err.println("Caught " + e);
}
}
}
class LoggingException extends Exception{
private static Logger logger = Logger.getLogger("LoggingException");
public LoggingException(){
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
记录已有异常信息
package com.mzm.chapter12;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
/**
* 使用Logger对象记录已有异常类信息
*
*/
public class LoggingExceptions2 {
private static Logger logger = Logger.getLogger("LoggingExceptions2");
static void logException(Exception e) {
StringWriter trace = new StringWriter();
e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (Exception e) {
logException(e);
}
}
}
12.5 异常说明
throws关键字,紧跟在方法参数之后,表明该方法可能会抛出的异常。throws关键字后接的异常都是编译时异常,如果在方法体内产生了以异常,则该方法的异常说明中必须包含抛出的这类异常,否则编译不通过。
12.6 捕获所有异常
可以使用catch(Exception e)来捕获所有异常,但最好放置在处理程序的末尾。
Exception的方法:
String getMessage():获取异常的详细信息;
String getLocalizedMessage():同上,只是使用本地语言;
void printStackTrace():使用标准错误流打印Throwable和Throwable的调用栈轨迹;
void printStackTrace(PrintStream):与上面类似,只是使用打印流操作;
void printStackTrace(java.io.PrintWriter):同上;
Throwable fillInStackTrace():用于在Throwable对象的内部记录栈帧的当前状态。
12.6.1 栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(该Throwable被创建和抛出之处)。数组中的最后一个元素和栈底则是调用序列中的第一个方法调用。
package com.mzm.chapter12;
/**
* 调用栈
*
*/
public class WhoCalled {
static void f() {
try {
throw new Exception();
} catch (Exception e) {
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();
System.out.println("---------------------------------------");
h();
}
}
12.6.2 重新抛出异常
可以在捕获异常后,在处理程序中将其重新抛出,交由高一级环境处理。
但是printStackTrace()方法仍然显示的是原来的异常的抛出点,使用fillInStackTrace()可以改变异常的抛出点。
package com.mzm.chapter12;
/**
* 异常抛出点的修改
*
*/
public class Rethrowing {
public static void f() throws Exception {
System.out.println("originating the exception in f()");
throw new Exception("throw from f()");
}
public static void g() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace();
throw e;
}
}
public static void h() throws Exception {
try {
g();
} catch (Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace();
//使用fillInStackTrace()方法改变异常的抛出点
throw (Exception) e.fillInStackTrace();
}
}
public static void main(String[] args){
try{
g();
}catch (Exception e){
System.out.println("main: printStackTrace()");
//异常的抛出点仍然是f()
e.printStackTrace(System.out);
}
System.out.println("---------------------------------------------");
try{
h();
}catch (Exception e){
System.out.println("main: printStackTrace()");
//异常的抛出点已经改为了h()
e.printStackTrace(System.out);
}
}
}
如果在捕捉异常后,在处理程序中抛出另一种异常,则异常抛出点也会改变,原来异常抛出点的信息会丢失。
package com.mzm.chapter12;
/**
* 重新抛出另一种异常后,异常抛出点会发生改变
*/
public class RethrowNew {
public static void f() throws OneException {
System.out.println("originating the exception in f()");
throw new OneException("throw 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);
//异常抛出点为f()
throw new TwoException("from inner try");
}
}catch (TwoException e){
System.out.println("Caught in out try, e.printStackTrace()");
//异常的抛出点已变为main
e.printStackTrace(System.out);
}
}
}
class OneException extends Exception{
public OneException(String s){
super(s);
}
}
class TwoException extends Exception{
public TwoException(String s){
super(s);
}
}
12.6.3 异常链
在捕获一个异常后抛出另一个异常,并希望把原始异常的信息保存,这被称为异常链。
Throwable的子类在构造器中都可以接受一个cause对象最为参数,这个cause就表示原始异常,这样就可以把原始异常传递给新异常,使得即使在当前位置创建并抛出了新异常,也能通过异常链追踪到异常最初发生的位置。
注意,只有Error、Exception和RuntimeException三个Throwable的子类提供了带cause参数的构造器。如果要把其他类型的异常添加到异常链中来,应该使用initCause()方法。
package com.mzm.chapter12;
/**
* Created by 蒙卓明 on 2017/10/21.
*/
public class DynamicFields {
//内置二维数组,第一位表示字段标识符,第二位表示字段值(Object)
private Object[][] fields;
/**
* 构造器,根据初始长度构造内置二维数组
* @param initialSize 初始长度
*/
public DynamicFields(int initialSize){
fields = new Object[initialSize][];
for(int i = 0; i < initialSize; i++){
fields[i] = new Object[]{null, null};
}
}
/**
* 打印
* @return 打印内容
*/
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();
}
/**
* 判断内置二维数组是否包含指定的标识符
* @param id 标识符
* @return 包含则返回其在二维数组的位置,没有则返回-1
*/
private int hasField(String id){
for(int i = 0; i < fields.length; i++){
if(id.equals(fields[i][0])){
return i;
}
}
return -1;
}
/**
* 获取指定标识符
* @param id 指定标识符
* @return 标识符所在的位置
* @throws NoSuchFieldException 没有该标识符时抛出异常
*/
private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if(fieldNum == -1){
throw new NoSuchFieldException();
}
return fieldNum;
}
/**
* 向内置二维数组添加一个标识符
* @param id
* @return
*/
private int makeField(String id){
for(int i = 0; i < fields.length; i++){
if(fields[i][0] == null){
//二维数组未满
fields[i][0] = id;
return i;
}
}
//二维数组已满,创建新的二维数组
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;
//递归返回
return makeField(id);
}
/**
* 获取指定标识符对应的内容
* @param id
* @return
* @throws NoSuchFieldException
*/
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
/**
* 更新指定标识符所对应的字段值
* @param id 指定标识符
* @param value 新字段值
* @return
* @throws DynamicFieldsException
*/
public Object setField(String id, Object value) throws DynamicFieldsException {
if(value == null){
//新字段值为null,则抛自定义异常,注意这个自定义异常并没有提供带cause参数的构造器,要想实现异常转型,需使用initCause()方法指定
DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException());
throw dfe;
}
//判断是不是有这个标志符,没有就添加该标识符
int fieldNumber = hasField(id);
if(fieldNumber == -1){
fieldNumber = makeField(id);
}
Object result = null;
try{
result = getField(id);
} catch (NoSuchFieldException e) {
//可以直接异常转型
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args){
DynamicFields df = new DynamicFields(3);
System.out.println(df);
try{
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.println(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.println("df: " + df);
System.out.println("df.getField(\"d\") : " + df.getField("d"));
Object field = df.setField("d", null);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
}
}
}
class DynamicFieldsException extends Exception{
}