java编程思想第12章笔记
1.概念
异常指的是在程序运行过程中发生的异常事件,通常是由硬件问题或者程序设计问题所导致的。在Java等面向对象的编程语言中异常属于对象。
早期的C语言的异常处理机制,通常是我们人为的对返回结果加一些标志来进行判定,比如发生错误返回什么标志,正常情况下我们又是返回什么标记,而这些都不是语言本身所赋予我们的,这种机制的问题在于,C语言的异常处理机制全是我们人为的定义,这样就会造成业务逻辑的主线受到异常处理的牵制,或者说是我们难免会将注意力转移,并且造成业务逻辑与异常处理之间有很大程度上的缠绕。
2. 基本异常
异常清醒是指阻止当前方法或者作用域继续执行的问题.
在java中异常同样也是对象, 使用new可以在堆上创建异常对象
举个异常最常见的例子好了, 我们都知道除数是不能为零的, , 所以做除法之前要做一个必要的检查, 检查除数不能为零, 否则将抛出异常
另一种情况就是当使用一个对象的时候, 这个对象可能为null, 并没有初始化, 遇到这样的情况就需要抛出异常:
if (t == null)
throw newNull PointerExcept();
2.1 异常参数
所有标准的异常类都有两个构造器, 一个是默认构造器, 另一个是字符串为参数, 以便能把相关信息方法异常对象的构造器
3 捕获异常
3.1 try块
如果在方法内部抛出了异常, 那么这个方法将在抛出异常的过程中结束, 如果不想方法结束, 需要将可能产生异常的代码放入try代码块中, 如果产生异常就会被try块捕获
3.2 异常处理程序
当异常抛出后, 需要被处理, 处理需要在catch块中进行, 对于每个catch自居, 接收一个且仅接收一个特殊类型的的参数的方法, catch块就是异常处理程序, 且异常处理程序必须紧跟try
4 创建自定义异常
自定义的异常类必须继承异常类, 最简单的就是继承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();
} catch (SimpleException e) {
System.out.println("Caught it");
}
}
}
Throw SimpleException from f()
Caught it
下面的例子将结果打印到了控制台上了, 更好的做法是答应道System.err, 因为System.out可能会被重定向, 同时这个例子也展示了一个接受字符串参数的构造器:
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) {
e.printStackTrace(System.out);
}
try {
g();
} catch (MyException e) {
e.printStackTrace(System.out);
}
}
}
输出结果:
Throwing MyException from f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at FullConstructors.main(FullConstructors.java:20)
Throwing MyException from g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at FullConstructors.main(FullConstructors.java:25)
说一下这个printStackTrace()方法, 这个方法将打印"从方法调用出知道异常跑出处"的方法调用序列, 这里是将信息发送到了System.out, 无参数版本就是答应道标准错误流
4 异常与记录日志
使用java.util.Logging工具可以将输出记录到日记中, 基本的使用如下:
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());
}
}
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);
}
}
}
输出如下
十二月 01, 2016 9:14:31 下午 LoggingException
严重: LoggingException
at LoggingExceptions.main(LoggingExceptions.java:16)
Caught LoggingException
十二月 01, 2016 9:14:31 下午 LoggingException
严重: LoggingException
at LoggingExceptions.main(LoggingExceptions.java:23)
Caught LoggingException
静态的Logger.getLogger()方法创建了一个String参数相关的Logger对象, 通常与错误相关的类名一致, 这个Logger对象会将其输出发送到System.err.
severe()方法表示级别, 很明显, severe表示严重的
但是更常见的情况是我们需要捕获和记录别人编写的异常, 比如说这样:
import java.util.logging.*;
import java.io.*;
public class LoggingException2 {
private static Logger logger = Logger.getLogger("LoggingException2");
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);
}
}
}
输出结果为:
十二月 01, 2016 9:24:49 下午 LoggingException2 logException
严重: java.lang.NullPointerException
at LoggingException2.main(LoggingException2.java:14)
当然可以进一步自定义异常, 比如加入额外的构造器和成员, 得到更强大的功能.
5.异常说明
使用throws关键字, 紧跟在方法后面, 就像上面的程序例子一样, 说明了这个方法可能会抛出的异常类型, 这就是异常说明
6. 捕获所有的异常
可以只使用一个异常处理程序来捕获所有类型的异常, 因为异常类型的基类都是Exception;
catch(Exception e) {
System.out.println("Caught an Exception");
}
6.1 栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问, 这个方法返回一个由栈轨迹中的元素所构成的数组, 其中每一个元素表示栈中的一帧, 元素0是栈顶元素, 并且是调用序列中最后一个被调用的方法, 数组中的最后一个元素是调用序列中的第一个方法
下面是一个简单的演示程序:
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();
}
}
输出结果:
f
main
--------------------------------------
f
g
main
--------------------------------------
f
g
h
main
6.2 重新抛出异常
有时候需要将刚捕获的异常重新抛出, 尤其是使用Exception捕获所有异常的时候, 可以直接把它重新抛出, 重新抛出异常会把异常抛给上一级环境中的异常处理程序, 此外异常对象的所有信息都得以保存.
如果只是重新把当前的异常对象抛出, 那么printStrackTrace()方法现实的将是原来异常抛出点的调用栈信息, 并非重新抛出点的信息.
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 {
g();
} catch (Exception e) {
System.out.println("inside h(), e.printStackTrace()");
e.printStackTrace(System.out);
// throw (Exception) e.fillInStackTrace();
throw e;
}
}
public static void main(String[] args) {
try {
g();
} catch (Exception e) {
System.out.println("main : printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch (Exception e) {
System.out.println("main : printStackTrace()");
e.printStackTrace(System.out);
}
}
}
首先执行这个结果
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
去掉注释并且将throw e;注释掉, 结果是:
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.main(Rethrowing.java:29)
Originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:9)
at Rethrowing.h(Rethrowing.java:19)
at Rethrowing.main(Rethrowing.java:36)
main : printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:23)
at Rethrowing.main(Rethrowing.java:36)
对比第二个结果比第一个结果少了倒数三四行, fillInStackTrace方法会将栈轨迹清空并填充当前栈轨迹
6.3 异常链
常常会想要在捕获一个异常之后抛出另一个异常, 并想吧原始异常的信息保存下来, 这样称为异常链, throwable中initCause()方法可以接受一个cause对象作为参数, 这个cause就是用来表示原始异常的, 这样通过把原始异常传递给新的异常, 是的即使在当前位置创建并抛出了新的异常, 也能通过这个异常链追踪到最初发生异常的地方.
下面看一个可以动态的扩展字段的dome:
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();
}
// 判断是否存在该字段, 存在返回下标, 如果不存在该字段返回-1
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;
}
}
Object[][] temp = new Object[fields.length +1][2];
for (int i = 0; i < fields.length; i++) {
temp[i] = fields[i];
}
for (int i = fields.length; i < temp.length; i++) {
temp[i] = new Object[] {null, null};
}
fields = temp;
return makeField(id);
}
// 得到该字段标识符的对应的字段值
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
// 设置字段
public Object setField(String id, Object value) throws DynamicFieldsException {
// 字段值为空的话
if (value == null) {
DynamicFieldsException dfe = new DynamicFieldsException();
// 将此throwable的case初始化为指定值
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);
System.out.println("df.getField(\"d\")" + df.getField("d"));
Object field = df.setField("d", null); // 产生异常
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
运行后的输出结果为:
null:null
null:null
null:null
d:A value for d
number:47
number2:48
d:A new value for d
number:47
number2:48
number3:11
df.getField("d")A new value for d
DynamicFieldsException
at DynamicFields.setField(DynamicFields.java:64)
at DynamicFields.main(DynamicFields.java:94)
Caused by: java.lang.NullPointerException
at DynamicFields.setField(DynamicFields.java:65)
... 1 more