12.1 概念
异常可以将“在正常时候执行的代码”和“发生错误时的代码”相分离,达到结构清晰的目的。
a.受检查异常checkedException 编译器强制要求我们必须处理的异常,典型:除了RuntimeException及其子类+各种Error,通常我们需要对这些异常进行处理
b.非检查异常uncheckedException我们不进行处理Java不会报错 因为Java虚拟机会对他们进行处理,典型:RuntimeException及其子类+各种Error,通常我们处理不了这些异常,或者处理的代价远大于抛出。
c.Exception可以分为运行时异常(可以不捕获)和非运行时异常(必须捕获)
12.2 基本异常
异常分为两种
1.当前环境可以处理,那就在当前方法处理
2.当前环境不可以处理,需要将异常抛给方法调用者,让其处理
异常是一个事务(没有理解,是指抛出异常后所有执行无效?)
12.2.1异常参数
异常的创建:两种,一种无参 一种是带一个字符串作为参数
throw可以将异常抛出给调用者,异常抛出时退出当前作用域或方法
Throwable 是异常的基类,错误信息通常包含在异常类名称或保存在异常对象内部,接受异常的地方通常通过这些信息来处理异常
12.3 捕获异常
基本语法
try {
//可能异常
} catch (ExceptionType1 e1) {
//处理异常
} catch (ExceptionType2 e2) {
//处理异常
} catch (ExceptionType3 e3) {
//处理异常
} finally{
//必定执行的代码段
}
终止与恢复
前面我们提到异常可以抛出给调用者(终止模型)也可以在方法内部处理,使其回归到正常状态(恢复模型)
虽然恢复模型看起来很有诱惑力,但是实际执行的时候可能处理错误的代码非常难以维护,所以通常在这种方式的选择上需要一定的平衡
12.4 自定义异常
public class SimpleException extends Exception{
}
public class InheritingExceptions {
int x = 0;
public void f() throws SimpleException {
System.out.println("Throw SimpleException from f()");
x++;
if (x == 1) {
throw new SimpleException();
}
}
public static void main(String[] args) {
InheritingExceptions sed = new InheritingExceptions();
try {
sed.f();
} catch (SimpleException e) {
System.out.println("Caught it!");
}
}
}
//本例看起来好像没有什么用,但实际应用时这种例子可以覆盖大部分情况,就像之前提到的,异常类的名字很关键
定义接受字符串做参数的构造器
public class MyException extends Exception {
public MyException() {
}
public MyException(String msg){
//增加含参构造方法,也很简单
super(msg);
}
}
public class FullConstructors {
public static void f() throws MyException{
System.out.println("Throw MyException from f()");
throw new MyException();
}
public static void g() throws MyException{
System.out.println("Throw MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch (MyException exception) {
//exception.printStackTrace();//输出到标准错误流
exception.printStackTrace(System.out);
}
try {
g();
} catch (MyException exception) {
exception.printStackTrace(System.out);
}
}
}
/*输出
Throw MyException from f()
ex12d4.MyException
at ex12d4.FullConstructors.f(FullConstructors.java:6)
at ex12d4.FullConstructors.main(FullConstructors.java:16)
Throw MyException from g()
ex12d4.MyException: Originated in g()
at ex12d4.FullConstructors.g(FullConstructors.java:11)
at ex12d4.FullConstructors.main(FullConstructors.java:22)
**/
12.4.1 异常与记录日志
抛出异常
import java.util.logging.*;
import java.io.*;
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());//利用输入输出流向logger对象写入错误信息
}
}
public class LoggingExceptions {
public static void main(String[] args) {
try {
throw new LoggingException();//抛出异常
} catch(LoggingException e) {
System.err.println("Caught " + e);
}
try {
throw new LoggingException();
} catch(LoggingException e) {
System.err.println("Caught " + e);
}
}
} /* Output:
Jun 18, 2019 8:57:36 AM ex12d4d1.LoggingException <init>
SEVERE: ex12d4d1.LoggingException
at ex12d4d1.LoggingExceptions.main(LoggingExceptions.java:20)
Caught ex12d4d1.LoggingException
Jun 18, 2019 8:57:36 AM ex12d4d1.LoggingException <init>
SEVERE: ex12d4d1.LoggingException
at ex12d4d1.LoggingExceptions.main(LoggingExceptions.java:25)
Caught ex12d4d1.LoggingException
*///:~
捕获异常
import java.util.logging.*;
import java.io.*;
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(NullPointerException e) {
logException(e);//捕获异常
}
}
} /* Output:
Jun 18, 2019 9:22:13 AM ex12d4d1.LoggingExceptions2 logException
SEVERE: java.lang.NullPointerException
at ex12d4d1.LoggingExceptions2.main(LoggingExceptions2.java:16)
*///:~
自定义异常
//自定义异常
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; }
public String getMessage() {//看起来该方法初始化时会被父类调用
return "Detail Message: "+ x + " "+ super.getMessage();
}
}
public class ExtraFeatures {
public static void f() throws MyException2 {//抛出无参自定义异常
System.out.println("Throwing MyException2 from f()");
throw new MyException2();
}
public static void g() throws MyException2 {//抛出包含string的自定义异常
System.out.println("Throwing MyException2 from g()");
throw new MyException2("Originated in g()");
}
public static void h() throws MyException2 {//抛出包含string和int值的自定义异常
System.out.println("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());
}
}
} /* Output:
Throwing MyException2 from f()
ex12d4d1.MyException2: Detail Message: 0 null
at ex12d4d1.ExtraFeatures.f(ExtraFeatures.java:22)
at ex12d4d1.ExtraFeatures.main(ExtraFeatures.java:34)
Throwing MyException2 from g()
ex12d4d1.MyException2: Detail Message: 0 Originated in g()
at ex12d4d1.ExtraFeatures.g(ExtraFeatures.java:26)
at ex12d4d1.ExtraFeatures.main(ExtraFeatures.java:39)
Throwing MyException2 from h()
ex12d4d1.MyException2: Detail Message: 47 Originated in h()
at ex12d4d1.ExtraFeatures.h(ExtraFeatures.java:30)
at ex12d4d1.ExtraFeatures.main(ExtraFeatures.java:44)
e.val() = 47
*///:~
12.5 异常说明
在方法中告知调用者可能抛出的异常就是异常说明
比如FileWriter的append方法可能会抛出异常,它的方法是这样的
/**
* Appends the specified character sequence to this writer.
*
* <p> An invocation of this method of the form <tt>out.append(csq)</tt>
* behaves in exactly the same way as the invocation
*
* <pre>
* out.write(csq.toString()) </pre>
*
* <p> Depending on the specification of <tt>toString</tt> for the
* character sequence <tt>csq</tt>, the entire sequence may not be
* appended. For instance, invoking the <tt>toString</tt> method of a
* character buffer will return a subsequence whose content depends upon
* the buffer's position and limit.
*
* @param csq
* The character sequence to append. If <tt>csq</tt> is
* <tt>null</tt>, then the four characters <tt>"null"</tt> are
* appended to this writer.
*
* @return This writer
*
* @throws IOException
* If an I/O error occurs
*
* @since 1.5
*/
public Writer append(CharSequence csq) throws IOException {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
12.6 捕获所有异常
catch (Exception e) {
// TODO: handle exception
}
这样的写法可以捕获所有异常,因为Exception是所有异常的基类
但是这要写在捕获异常的末端,因为如果写在前面就会提前捕获,而子类exception的信息会更加详细
比如这样
try {
object.toString();
} catch (NullPointerException exception) {
} catch (Exception e) {
// TODO: handle exception
}
例子:打印异常状态的方法
常用方法有这几个
e.getMessage();
e.getLocalizedMessage();
e.toString();
e.printStackTrace();
e.printStackTrace(PrintStream);
e.printStackTrace(PrintWriter);
例子
// Demonstrating the Exception Methods.
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch(Exception e) {
System.out.println("1 Caught Exception");
System.out.println("2 getMessage():" + e.getMessage());
System.out.println("3 getLocalizedMessage():" +
e.getLocalizedMessage());
System.out.println("4 toString():" + e);
System.out.print("5 printStackTrace():");
e.printStackTrace(System.out);
}
}
} /* Output:
1 Caught Exception
2 getMessage():My Exception
3 getLocalizedMessage():My Exception
4 toString():java.lang.Exception: My Exception
5 printStackTrace():java.lang.Exception: My Exception
at ex12d4.ExceptionMethods.main(ExceptionMethods.java:7)
*///:~
可以发现他们的输出信息 越来越详细。
12.6.1 栈轨迹
// Programmatic access to stack trace information.
public class WhoCalled {
static void methoedF() {
// Generate an exception to fill in the stack trace
try {
throw new Exception();
} catch (Exception e) {
// e.getStackTrace() 是一个StackTraceElement[]数组 存储了调用堆栈
// 可以打印方法的调用链
for(StackTraceElement ste : e.getStackTrace())
System.out.println(ste.getMethodName());
}
}
static void methoedG() {
methoedF();
}
static void methoedH() {
methoedG();
}
public static void main(String[] args) {
methoedF();
System.out.println("--------------------------------");
methoedG();
System.out.println("--------------------------------");
methoedH();
}
} /*
* Output:
methoedF
main
--------------------------------
methoedF
methoedG
main
--------------------------------
methoedF
methoedG
methoedH
main
*/// :~
把catch中的代码改为e.printStackTrace(); 输出如下
java.lang.Exception
at ex12d6.WhoCalled.methoedF(WhoCalled.java:9)
at ex12d6.WhoCalled.main(WhoCalled.java:28)
java.lang.Exception--------------------------------
at ex12d6.WhoCalled.methoedF(WhoCalled.java:9)
at ex12d6.WhoCalled.methoedG(WhoCalled.java:20)
--------------------------------
at ex12d6.WhoCalled.main(WhoCalled.java:30)
java.lang.Exception
at ex12d6.WhoCalled.methoedF(WhoCalled.java:9)
at ex12d6.WhoCalled.methoedG(WhoCalled.java:20)
at ex12d6.WhoCalled.methoedH(WhoCalled.java:24)
at ex12d6.WhoCalled.main(WhoCalled.java:32)
12.6.2 重新抛出异常
在catch语句中捕获到异常之后仍然可以继续将异常抛出给上级调用者
比如这样
catch(MyException2 e) {
e.printStackTrace(System.out);
throw e;
}
直接通过throw将异常抛给上一级将会使得异常和起初的异常信息保持一致 而通过e.fillInStackTrace();抛出,则会打印新的调用者的相关异常信息
// Demonstrating 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);
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args) {
try {
g();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
System.out.println("=================================");
try {
h();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at ex12d4d1.Rethrowing.f(Rethrowing.java:7)
at ex12d4d1.Rethrowing.g(Rethrowing.java:11)
at ex12d4d1.Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
at ex12d4d1.Rethrowing.f(Rethrowing.java:7)
at ex12d4d1.Rethrowing.g(Rethrowing.java:11)
at ex12d4d1.Rethrowing.main(Rethrowing.java:29)
=================================
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at ex12d4d1.Rethrowing.f(Rethrowing.java:7)
at ex12d4d1.Rethrowing.h(Rethrowing.java:20)
at ex12d4d1.Rethrowing.main(Rethrowing.java:36)
main: printStackTrace()
java.lang.Exception: thrown from f()
at ex12d4d1.Rethrowing.h(Rethrowing.java:24)
at ex12d4d1.Rethrowing.main(Rethrowing.java:36)
*///:~
16.3异常链
概念:在捕获一个异常后抛出另一个异常,并且保留原始的异常信息。
JDK1.4之前要程序员自己编写程序保留原始信息,现在可以通过Throwable子类构造器接受一个cause参数来保留,cause保存的就是原始信息,这样就可以传给新的异常了,这样一级级追踪,形成异常链
Throwable的三个字类提供了带cause参数的构造器,他们是Error Exception RuntimeException。要把其他类型异常链连接起来要使用initCause方法而不是构造器
如何链接其他异常的例子:
class DynamicFieldsException extends Exception {
}
public class DynamicFields {
private Object[][] fields;// 存值的二维数组
public DynamicFields(int initialSize) {// 二维数组固定为initialSize*2
fields = new Object[initialSize][2];// 可以想象成大小为initialSize的map
for (int i = 0; i < initialSize; i++)
fields[i] = new Object[] { null, 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) {// 判断“key”集合是否包含指定字符串
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 {
// 调用hasField方法
// 判断“key”集合是否包含指定字符串,如果包含,返回下标
// 否则
// 抛出NoSuchFieldException(NoSuchFieldException是java异常的一种)
int fieldNum = hasField(id);
if (fieldNum == -1)
throw new NoSuchFieldException();
return fieldNum;
}
private int makeField(String id) {
// 遍历二维数组 如果指定key是空,给所有为空的地方赋值为id并返回index
for (int i = 0; i < fields.length; i++)
if (fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// 走到这里说明没有空字段了 创建一个新的二维数组,行数比原来+1
Object[][] tmp = new Object[fields.length + 1][2];
for (int i = 0; i < fields.length; i++)
// copy旧值给新的数组
tmp[i] = fields[i];
for (int i = fields.length; i < tmp.length; i++)
// 新增的空间赋值为null, null
tmp[i] = new Object[] { null, null };
fields = tmp;
// Recursive call with expanded fields: 递归调用
return makeField(id);//下一次调用给最后一个元素key赋值并返回最后一个元素的下标
}
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
public Object setField(String id, Object value)//给指定key值的地方赋值为value 如果没有指定key(但是有key是null的,使用空位),没有空位 则新加一个
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();
dfe.initCause(new NullPointerException());
throw dfe;
}
int fieldNumber = hasField(id);//找到包含指定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); //想象成长度为3的map
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); // 尝试给key为“d”的“map”赋值空值 报错
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
} /*
* Output:
null: null
null: null
null: null
d: A value for d
number: 47
number2: 48
df: d: A new value for d
number: 47
number2: 48
number3: 11
df.getField("d") : A new value for d
ex12d6.DynamicFieldsException
at ex12d6.DynamicFields.setField(DynamicFields.java:77)
at ex12d6.DynamicFields.main(DynamicFields.java:107)
Caused by: java.lang.NullPointerException
at ex12d6.DynamicFields.setField(DynamicFields.java:78)
... 1 more
*/// :~
12.7 Java 标准异常
Java通常有Error和Exception 但程序员通常只关心exception
Java的异常通常可以从名字看出关键问题发生在什么地方
Java异常分类概要图
引用来自https://blog.csdn.net/claram/article/details/48133727
的异常分类图
对比我之前的图片更适合理解
12.7.1 RuntimeException
一个简单的例子
if (args == null) {
throw new NullPointerException();
}
如果我们需要为每一个方法参数都进行空指针判断 那就太冗余了,而且代码会显得混乱。其实java本身会检测标准运行时异常,这些异常会被Java抛出(但是你仍然可以写自己代码捕获异常以规避空指针异常)
运行时异常RuntimeException是基类,会被java检测并抛出(NullPointerException属于运行时异常),他们也被称为“不受检查异常”(可以不用接受程序的代码检测,仅靠Java来自动捕获)
如果没有捕获异常而发生了异常,会怎么样?
// Ignoring RuntimeExceptions.
// {ThrowsException}
public class NeverCaught {
static void f() {
throw new RuntimeException("From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
} ///:~
/*
Exception in thread "main" java.lang.RuntimeException: From f()
at ex12d6.NeverCaught.f(NeverCaught.java:7)
at ex12d6.NeverCaught.g(NeverCaught.java:10)
at ex12d6.NeverCaught.main(NeverCaught.java:13)
*/
12.8 使用finally清理
finally语句中的代码都是会被执行,finally常被用于进行状态恢复
class FourException extends Exception {
}
public class AlwaysFinally {
public static void main(String[] args) {
System.out.println("Entering first try block");
try {
System.out.println("Entering second try block");
try {
throw new FourException();
} finally {
System.out.println("finally in 2nd try block");
}
} catch (FourException e) {
System.out.println("Caught FourException in 1st try block");
} finally {
System.out.println("finally in 1st try block");
}
//即使发生异常 finally总会执行
}
} /*
* Output:
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block
*/// :~
12.8.2 在return中使用finally
public class MultipleReturns {
public static void f(int i) {
System.out.println("Initialization that requires cleanup");
try {
System.out.println("Point 1");
if(i == 1) return;
System.out.println("Point 2");
if(i == 2) return;
System.out.println("Point 3");
if(i == 3) return;
System.out.println("End");
return;
} finally {
System.out.println("Performing cleanup");
}
}
public static void main(String[] args) {
for(int i = 1; i <= 4; i++)
f(i);
}
} /* Output:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
*///:~
由于finally有必定执行的特征,在有多个返回点的程序中可以在finally中执行清理工作 而不会被遗漏
12.8.3 缺憾:异常丢失
//多个异常发生时 只会捕获一个异常,其他异常会被忽略。异常的优先级与与位置有关,优先级如下
// finally语句中>外层try catch中捕获的异常>同级异常先后发生的顺序
//在finally中直接进行return返回将会忽略所有异常。
class VeryImportantException extends Exception {
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
public String toString() {
return "A trivial exception";
}
}
public class LostMessage {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args) {
try {
LostMessage lm = new LostMessage();
try {
lm.f();
} finally {
lm.dispose();
}
} catch(Exception e) {
System.out.println(e);
}
}
} /* Output:
A trivial exception
*///:~
12.9 异常的限制
(参考https://zachary-guo.iteye.com/blog/346102)
这一节书中讲的比较乱,我自己找了一个网址比较清晰
异常处理的继承
part1关于构造方法(只关心父类构造方法抛出的非Runtime异常)
1.父类有无参构造函数且未声明任何异常的抛出
子类默认调用父类无参构造方法,因此子类可以抛出或不抛出异常
2.父类有无参构造函数且声明了非Runtime异常抛出
子类默认调用父类无参构造方法,因此子类必须抛出或者捕获父类声明的异常或者异常的父类,当然子类也可以抛出其他异常
3.父类有无参构造函数且声明了Runtime异常抛出
如果父类声明的是runtime异常,则子类可以不进行抛出或者捕获(Java会自动捕获)
4.父类有有参的构造函数
不管父类的有参构造函数是否抛出异常,子类都不必关心,因为子类只会调用父类的无参构造方法
part2关于普通方法(只关心父类普通方法抛出的非Runtime异常)
5.父类的方法未声明任何异常的抛出
子类覆写此方法时不能声明任何异常(除了 RuntimeException)的抛出。
Parent P = new Child();
/**
- 由于 Parent 声明调用 hello 方法时不会出现异常,可是 Child 覆写 hello 方法时却声明需要抛出异常。
- 根据多态,以下的调用将调用 Child 的 hello 方法(需捕捉异常),而调用者却是 Parent,Parent 声明
- 没有异常。因此暴露給我们的是 Parent 引用,我们不会去捕捉异常,这样就矛盾了,所以子类覆写父类时
- 不能抛出异常。
*/
p.hello();
6.父类的方法声明了异常(RuntimeException)的抛出
子类可以不做异常抛出的声明,也可以声明任何 RuntimeException 的抛出
7.父类的方法声明了异常(非 RuntimeException)的抛出
子类可以不做异常抛出的声明。一旦声明了,那么其异常类型至多和父类声明的异常类型一样,包括可以是父类异常类型的子类。
part3关于继承父类和实现接口
8.子类继承父类的同时还实现了某一接口
若接口和父类有同样的函数签名,声明抛出的非Runtime异常类型应该与父类保持一致,这个与第七点类似。(个人观点)否则在多态的时候无法确认是否捕获了正确的异常
12.10 构造器
// 注意构造器中的异常(构造还没有完成,有些异常需要酌情考虑,finally清理程序不是万能的)
import java.io.*;
public class InputFile {
private BufferedReader in;
public InputFile(String fname) throws Exception {//构造器
try {
in = new BufferedReader(new FileReader(fname));
// 其他可能引起异常的code
} catch(FileNotFoundException e) {
System.out.println("Could not open " + fname);
//文件还没有打开,所以不需要close
throw e;
} catch(Exception e) {
// 其他异常,需要close文件
try {
in.close();//close操作也会失败,需要继续try catch
} catch(IOException e2) {
System.out.println("in.close() unsuccessful");
}
throw e; // 重新抛出
} finally {
//不应该在这里close文件
}
}
public String getLine() {//返回文件的下一行内容
String s;
try {
s = in.readLine();
} catch(IOException e) {
throw new RuntimeException("readLine() failed");
}
return s;
}
public void dispose() {//释放资源的方法 不要试图在finalize方法关闭资源 而应该告诉调用者
try {
in.close();
System.out.println("dispose() successful");
} catch(IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
} ///:~
确保正确清理资源的例子 在创建出一个需要释放资源的对象时,立即紧跟上一个finally语句用于清理资源
public class Cleanup {//对于在构造方法中可能有异常的对象的创建,使用嵌套的try catch更安全
public static void main(String[] args) {
try {
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while((s = in.getLine()) != null)
; // Perform line-by-line processing here...
} catch(Exception e) {//构建成功的异常处理
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
} finally {//构造成功必定会清理 //紧跟的资源清理finally
in.dispose();
}
} catch(Exception e) {//构建失败的异常处理
System.out.println("InputFile construction failed");
}
}
} /* Output:
*///:~
每一个需要清理的对象都必须跟着 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 {}
}
public class CleanupIdiom {
public static void main(String[] args) {
// Section 1:
NeedsCleanup nc1 = new NeedsCleanup();//创建构造器不会抛出异常的类的对象
try {
// ...
} finally {
nc1.dispose();//finally清理
}
// Section 2:
// If construction cannot fail you can group objects:
NeedsCleanup nc2 = new NeedsCleanup();//创建构造器不会抛出异常的类的对象
NeedsCleanup nc3 = new NeedsCleanup();//创建构造器不会抛出异常的类的对象
try {
// ...
} finally {
nc3.dispose(); //顺序与构造顺序相反(先创建的后释放)
nc2.dispose();
}
// Section 3:
// 如果构造方法可能抛出异常 则需要处理异常情况和正常情况:
try {
NeedsCleanup2 nc4 = new NeedsCleanup2();//
try {
NeedsCleanup2 nc5 = new NeedsCleanup2();
try {
// ...
} finally {
nc5.dispose();//nc5构造成功
}
} catch(ConstructionException e) { // nc5 构造异常
System.out.println(e);
} finally {//nc5创建异常 但是nc4创建成功 需要清理
nc4.dispose();
}
} catch(ConstructionException e) { // nc4构造发生异常
System.out.println(e);
}
}
} /* Output:
NeedsCleanup 1 disposed
NeedsCleanup 3 disposed
NeedsCleanup 2 disposed
NeedsCleanup 5 disposed
NeedsCleanup 4 disposed
*///:~
12.11 异常匹配(父类异常与子类异常 及其书写顺序)
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
public static void main(String[] args) {
// 对于一个try catch 只会捕捉最“近”发生的异常
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");
}
}
} /* Output:
Caught Sneeze
Caught Annoyance 2
*///:~
12.12 其他可选方式 讨论受检查异常的好处和坏处 作者认为坏处更大
受检查异常的来由:
在早先的其他语言中(比如C),处理异常必须执行条件测试,以确定异常类型,这样会导致程序可读性降低并且可能导致性能下降,所以程序员不愿意处理异常。受检查异常就是为了让程序员解决其他语言中出现的这种问题。
受检查异常的好处和坏处
好处:正常情况的代码和错误处理代码相分离
坏处:
1.强制我们处理异常,即使有时候我们由于条件不全,无法处理相关异常
2.可能导致我们虽然使用try捕获到了异常 但是在catch中没有处理异常的欺骗性代码,导致异常“消失”
个人觉得这一章讲的太详细了 实际使用时很多东西其实用不到,不过详细也是编程思想的特征,如果觉得看不下去可以看精简版的effective Java